/*
 Navicat MySQL Data Transfer

 Source Server         : hyf
 Source Server Type    : MySQL
 Source Server Version : 80027
 Source Host           :
 Source Schema         : jvav

 Target Server Type    : MySQL
 Target Server Version : 80027
 File Encoding         : 65001

 Date: 18/11/2021 17:54:37
*/
create database jvav;
use jvav;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
  `id` int NOT NULL AUTO_INCREMENT,
  `uid` int DEFAULT NULL,
  `agree` int DEFAULT '0',
  `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文章标题',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `date` timestamp NOT NULL,
  `image` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '文章封面',
  `star` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `uid` (`uid`) USING BTREE,
  CONSTRAINT `article_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of article
-- ----------------------------
BEGIN;
INSERT INTO `article` VALUES (1, 1, 6, '栈和队列的转换上', '@[TOC](超易理解的栈和队列)\r\n## 栈和队列的转换上\r\n我们通过一系列栈的压入和弹出操作来分析用两个队列模拟-一个栈的过程。如图所示，去满五年先在queue1中压入abc三个元素，只要有进队 ，必压入queue1中     弹出的时候   若queue2为空，则吧queue1中的元素依次弹出并压入queue2中   此时queue2中只要弹出元素   必是和压入顺序一致  即先进先出\r\n\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200405085413391.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n话不多说  上代码 \r\n\r\n\r\n\r\n\r\nclass MyQueue {\r\npublic:\r\n    /** Initialize your data structure here. */\r\n    stack<int> s1;\r\n    stack<int> s2;\r\n    MyQueue() {\r\n\r\n    }\r\n    \r\n    /** Push element x to the back of queue. */\r\n    void push(int x) {\r\n        s1.push(x);\r\n    }\r\n    \r\n    /** Removes the element from in front of queue and returns that element. */\r\n    int pop() {\r\n        if(s2.size()==0)\r\n        {\r\n            while(s1.size()!=0)\r\n            {\r\n                s2.push(s1.top());\r\n                s1.pop();\r\n            }\r\n        }\r\n        int res=s2.top();\r\n        s2.pop();\r\n        return res;\r\n    }\r\n    \r\n    /** Get the front element. */\r\n    int peek() {\r\n        if(s2.size()==0)\r\n        {\r\n            while(s1.size()!=0)\r\n            {\r\n                s2.push(s1.top());\r\n                s1.pop();\r\n            }\r\n        }\r\n        int res=s2.top();\r\n        \r\n        return res;\r\n    }\r\n    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200405090159472.png)\r\n    /** Returns whether the queue is empty. */\r\n    bool empty() {\r\n        if(s1.empty()&&s2.empty())\r\n            return true;\r\n        return false;\r\n    }\r\n};\r\n\r\n\r\n关注订阅号<偏执的喵喵>即可领取众多编程电子书籍哦![在这里插入图片描述](https://img-blog.csdnimg.cn/20200405090307934.png)\r\n\r\n', '2020-11-14 16:06:07', NULL, 0);
INSERT INTO `article` VALUES (2, 1, 9, '快排的递归实现', '# 快排的递归实现\r\n#### partition的几种实现\r\n第一个用首元素划分\r\n第二个用尾元素划分\r\n\r\n\r\n```cpp\r\n/*1.  使用while的partion*/\r\nint partition2(vector<int>&vec, int start, int end)\r\n{\r\n	int i = start, j = end;\r\n	int key = vec[start];\r\n	while (i < j)\r\n	{\r\n		while (i < j&&vec[j] >= key)\r\n			j--;\r\n		if (i < j)\r\n			vec[i++] = vec[j];\r\n		while (i < j&&vec[i] < key)\r\n			i++;\r\n		if (i < j)\r\n			vec[j--] = vec[i];\r\n	}\r\n	vec[i] = key;\r\n	return i;\r\n}\r\n\r\n\r\n\r\n/*2.  使用for的partion*/\r\nint partition1(vector<int>&vec, int start, int end)\r\n{\r\n	int key = vec[end];\r\n	int small = start - 1;\r\n	for (int i = start; i < end; i++)\r\n	{\r\n		if (vec[i] < vec[end]  )\r\n		{\r\n			small++;\r\n			swap(vec[small] , vec[i]);\r\n		}\r\n	}\r\n	small++;\r\n	swap(vec[small], vec[end]);\r\n	return small;\r\n}\r\n  \r\n\r\n\r\n```\r\n## 快排的解决代码\r\n\r\n\r\n```cpp\r\n//结合partition解决\r\nvoid quickSort(int a[], int p, int r) {\r\n  int position = 0;\r\n  if (p<r)\r\n  {\r\n    position = partition(a,p,r);//返回划分元素的最终位置\r\n    quickSort(a,p,position-1);//划分左边递归\r\n    quickSort(a, position + 1,r);//划分右边递归\r\n  }\r\n}\r\n\r\n\r\n//2.单独解决（为partion和sort的结合体）   可重点背诵\r\nvoid quick(int a[],int left,int right)\r\n{\r\n	if(left>=right)\r\n		return;\r\n	int i=left,j=right,key=a[left];\r\n	while(i<j)\r\n	{\r\n		while((i<j)&&a[j]>=key)\r\n			j--;\r\n		if(i<j)\r\n			a[i++]=a[j];\r\n		while((i<j)&&a[i]<key)\r\n			i++;\r\n		if(i<j)\r\n			a[j--]=a[i];\r\n	}\r\n	a[i]=key;\r\n	quick(a,left,j-1);\r\n	quick(a,j+1,right);\r\n}\r\n```\r\n\r\n如果想要性能提升，还可以使用随机作为划分\r\n\r\n```cpp\r\nint partition1(vector<int>&vec, int start, int end)\r\n{\r\n	int index = start+rand()%(end-start+1);//元素变化范围在[start,end] \r\n	swap(vec[index],vec[end]);\r\n	int small = start - 1;\r\n	for (int i = start; i < end; i++)\r\n	{\r\n		if (vec[i] < vec[end]  )\r\n		{\r\n			small++;\r\n			swap(vec[small] , vec[i]);\r\n		}\r\n	}\r\n	small++;\r\n	swap(vec[small], vec[end]);//此时[start,small-1]是比vec[end]小的 \r\n	                           // [small,end-1]全是比vec[end]大的\r\n								//交换small和end    即实现左小右大\r\n	return small;\r\n}\r\n```\r\n**码字不易   日后还会继续学习   想要与某211的沙雕一起学的话  关注一哈订阅号 <偏执的喵喵>    持续分享一些学习记录与众多电子书籍    想要算法章节上课的ppt也有哦**', '2020-11-14 16:06:35', NULL, 0);
INSERT INTO `article` VALUES (3, 1, 4, 'ssm配置流程', '# ssm配置流程\r\n参考：**b站狂神说java**\r\n**https://space.bilibili.com/95256449?spm_id_from=333.788.b_765f7570696e666f.1**\r\n\r\n#### 自动装配注解需要的config\r\n**context:annotation-config**\r\n1.如果想使用@ Resource 、@ PostConstruct、@ PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor。\r\n2.如果想使用@PersistenceContext注解，就必须声明PersistenceAnnotationBeanPostProcessor的Bean。\r\n3.如果想使用@Autowired注解，那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。\r\n4.如果想使用 @Required的注解，就必须声明RequiredAnnotationBeanPostProcessor的Bean。\r\n\r\n**而使用context:annotation-config/ 就可以隐式地自动向Spring容器注册4个BeanPostProcessor**\r\n\r\n#### 理解三个注解的扫描或驱动\r\n1. <context:annotation-config /> 作用隐式的配置注解的加载类，默认的加载了AutowiredAnnotationBeanPostProcessor（autowired） ,@Resource、@PostConstruct、@PreDestroy等\r\n2.<mvc:annotation-driven/>  将reques 请求上面的数据绑定到controller 上\r\n3.<context:component-scan base-package=\"com.xiao.*\" />  自动扫描某个包下带有注解的类，并进行注解的相关操作，@Autowired 注入bean; **另外也包含了1 中所做的操作**\r\n\r\n\r\n\r\n\r\n## mybatis层  \r\n​创建项目\r\n##### ​导入依赖\r\n\r\n```xml\r\n<dependencies>\r\n  <!--Junit-->\r\n  <dependency>\r\n      <groupId>junit</groupId>\r\n      <artifactId>junit</artifactId>\r\n      <version>4.12</version>\r\n  </dependency>\r\n  <!--数据库驱动-->\r\n  <dependency>\r\n      <groupId>mysql</groupId>\r\n      <artifactId>mysql-connector-java</artifactId>\r\n      <version>8.0.19</version>\r\n  </dependency>\r\n  <!-- 数据库连接池 可选-->\r\n\r\n  <!--Servlet - JSP -->\r\n  <dependency>\r\n      <groupId>javax.servlet</groupId>\r\n      <artifactId>servlet-api</artifactId>\r\n      <version>2.5</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>javax.servlet.jsp</groupId>\r\n      <artifactId>jsp-api</artifactId>\r\n      <version>2.2</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>javax.servlet</groupId>\r\n      <artifactId>jstl</artifactId>\r\n      <version>1.2</version>\r\n  </dependency>\r\n\r\n  <!--Mybatis-->\r\n  <dependency>\r\n      <groupId>org.mybatis</groupId>\r\n      <artifactId>mybatis</artifactId>\r\n      <version>3.5.2</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>org.mybatis</groupId>\r\n      <artifactId>mybatis-spring</artifactId>\r\n      <version>2.0.2</version>\r\n  </dependency>\r\n\r\n  <!--Spring-->\r\n  <dependency>\r\n      <groupId>org.springframework</groupId>\r\n      <artifactId>spring-webmvc</artifactId>\r\n      <version>5.1.9.RELEASE</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>org.springframework</groupId>\r\n      <artifactId>spring-jdbc</artifactId>\r\n      <version>5.1.9.RELEASE</version>\r\n  </dependency>\r\n          <dependency>\r\n           <groupId>org.projectlombok</groupId>\r\n           <artifactId>lombok</artifactId>\r\n           <version>1.18.4</version>\r\n       </dependency>\r\n</dependencies>\r\n```\r\n##### 导入静态资源解析和java版本\r\n\r\n```xml\r\n<build>\r\n       <resources>\r\n           <resource>\r\n               <directory>src/main/java</directory>\r\n               <includes>\r\n                   <include>**/*.properties</include>\r\n                   <include>**/*.xml</include>\r\n               </includes>\r\n               <filtering>false</filtering>\r\n           </resource>\r\n           <resource>\r\n               <directory>src/main/resources</directory>\r\n               <includes>\r\n                   <include>**/*.properties</include>\r\n                   <include>**/*.xml</include>\r\n               </includes>\r\n               <filtering>false</filtering>\r\n           </resource>\r\n       </resources>\r\n       <plugins>\r\n           <plugin>\r\n               <groupId>org.apache.maven.plugins</groupId>\r\n               <artifactId>maven-compiler-plugin</artifactId>\r\n               <version>3.5</version>\r\n               <configuration>\r\n                   <source>1.8</source>\r\n                   <target>1.8</target>\r\n               </configuration>\r\n           </plugin>\r\n       </plugins>\r\n   </build>\r\n```\r\n##### 连接数据库		编写database.properties文件\r\n右侧直接添加  时区Asia/Shanghai\r\n\r\n```properties\r\njdbc.driver=com.mysql.cj.jdbc.Driver\r\njdbc.url=jdbc:mysql://localhost:3306/db1?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai\r\njdbc.username=root\r\njdbc.password=root\r\n```\r\n\r\n##### ​创建mybatis-config和applicationContext  创建实体类  \r\n**mybatis-config.xml**\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n<!DOCTYPE configuration\r\n       PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\"\r\n       \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\r\n<configuration>\r\n\r\n</configuration>\r\n```\r\n**applicationContext.xml**\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n      xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n       http://www.springframework.org/schema/beans/spring-beans.xsd\">\r\n\r\n</beans>\r\n```\r\n##### ​mybatis-config里写typealias取别名package      可添加日志\r\n\r\n```xml\r\n    <typeAliases>\r\n        <package name=\"com.mx.mapper\"/>\r\n    </typeAliases>\r\n```\r\n\r\n##### 写实体类，写dao的接口和***Mapper.xml文件\r\n自己写\r\n\r\n##### 写service的接口(函数直接复制dao接口的函数)  写service实现类(私有**Mapper成员和set方法以便注入)\r\n自己写\r\n## spring-dao层\r\n##### ​创建spring-dao.xml  添加到applicationContext.xml\r\n复制application.xml\r\n\r\n\r\n##### ​导入database.properties配置文件\r\njdbc.driver=com.mysql.cj.jdbc.Driver\r\njdbc.url=jdbc:mysql://localhost:3306/bookmanage?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai\r\njdbc.username=\r\njdbc.password=\r\n```xml\r\n<!--1. 关联数据库文件-->\r\n    <context:property-placeholder location=\"classpath:database.properties\"/>\r\n```\r\n\r\n##### 导入数据源(DriverManageDataSource)四信息\r\n复制application.xml\r\n\r\n\r\n##### ​注册SqlSessionFactory      需要注入数据源  注入configLocation为mybatis-config          还需要注入mapperLocation(作用是自动注册包下的mapper 就不用mybatis-config配置了)\r\n\r\n```xml\r\n<!--3. 注册sqlSessionFactory-->\r\n    <bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\">\r\n        <property name=\"dataSource\" ref=\"dataSource\"/>\r\n        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->\r\n        <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/>\r\n    </bean>\r\n```\r\n\r\n##### ​配置dao接口扫描包，动态实现dao接口注册到spring  参数有SqlSessionFactoryBeanName  和需要的扫描的包basepackage\r\n\r\n```xml\r\n<!-- 4.配置扫描Dao接口包，动态实现Dao接口注入到spring容器中\r\n        即自动生成需要在service中注入的bean   在spring-service中注入进去\r\n    -->\r\n    <!--解释 ：https://www.cnblogs.com/jpfss/p/7799806.html-->\r\n    <bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\">\r\n        <property name=\"sqlSessionFactoryBeanName\" value=\"sqlSessionFactory\"/>\r\n        <!-- 给出需要扫描Dao接口包 -->\r\n        <property name=\"basePackage\" value=\"com.mx.mapper\"/>\r\n    </bean>\r\n```\r\n\r\n## spring-service层\r\n##### ​创建spring-service.xml  添加到applicationContext.xml\r\n复制application.xml\r\n\r\n##### 扫描service下的包context的component-scan   (@Service时使用附带用autowired)\r\n\r\n```xml\r\n    <!--1. 扫描service层的注解 spring特有注解  扫描**ServiceImpl的@service-->\r\n    <context:component-scan base-package=\"com.mx.service\" />\r\n\r\n```\r\n\r\n##### 注册serviceImpl的bean  需要参数**Mapper (如果没有使用注解的话才注册)\r\n自己写\r\n##### 声明事务   DataSourceTransactionManager            参数注入数据源   (在需要事务的方法上注解@Transactional的即开启事务)\r\n\r\n```xml\r\n    <tx:annotation-driven/>\r\n<bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">\r\n       <property name=\"dataSource\" ref=\"dataSource\" />\r\n   </bean>\r\n\r\n```\r\n\r\n## spring-mvc层\r\n##### ​添加web支持  切换到web.xml\r\n##### 注册DispatcherServlet(参数选择applicationContext.xml)\r\n\r\n```xml\r\n   <!--1. DispatcherServlet-->\r\n   <servlet>\r\n       <servlet-name>DispatcherServlet</servlet-name>\r\n       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\r\n       <init-param>\r\n           <param-name>contextConfigLocation</param-name>\r\n           <!--一定要注意:这里加载的是总的配置文件，之前被这里坑了！-->\r\n           <param-value>classpath:applicationContext.xml</param-value>\r\n       </init-param>\r\n       <load-on-startup>1</load-on-startup>\r\n   </servlet>\r\n   <servlet-mapping>\r\n       <servlet-name>DispatcherServlet</servlet-name>\r\n       <url-pattern>/</url-pattern>\r\n   </servlet-mapping>\r\n```\r\n##### 注册乱码过滤器CharactorEncodingFilter\r\n\r\n```xml\r\n <!--2. encodingFilter-->\r\n    <filter>\r\n        <filter-name>encodingFilter</filter-name>\r\n        <filter-class>\r\n            org.springframework.web.filter.CharacterEncodingFilter\r\n        </filter-class>\r\n        <init-param>\r\n            <param-name>encoding</param-name>\r\n            <param-value>utf-8</param-value>\r\n        </init-param>\r\n    </filter>\r\n    <filter-mapping>\r\n        <filter-name>encodingFilter</filter-name>\r\n        <url-pattern>/*</url-pattern>\r\n    </filter-mapping>\r\n```\r\n\r\n##### 注册session过期时间\r\n\r\n```xml\r\n<!--3. Session过期时间-->\r\n   <session-config>\r\n       <session-timeout>15</session-timeout>\r\n   </session-config>\r\n```\r\n\r\n##### 创建spring-mvc.xml  添加到applicationContext.xml\r\n复制applicationContext.xml\r\n##### 切换到spring-mvc.xml\r\n##### ​注解驱动\r\n\r\n```xml\r\n    <!-- 1.开启SpringMVC注解驱动才可以使用@RequestMapping \r\n    	mvc特有-->\r\n    <mvc:annotation-driven />\r\n```\r\n##### 静态资源过滤\r\n\r\n```xml\r\n    <!-- 2.静态资源默认servlet配置-->\r\n    <mvc:default-servlet-handler/>\r\n```\r\n##### 视图解析器 InternalResourceViewResolver    参数是前后缀\r\n\r\n```xml\r\n<!-- 3.配置jsp 显示ViewResolver视图解析器 -->\r\n    <bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\r\n        <property name=\"viewClass\" value=\"org.springframework.web.servlet.view.JstlView\" />\r\n        <property name=\"prefix\" value=\"/WEB-INF/jsp/\" />\r\n        <property name=\"suffix\" value=\".jsp\" />\r\n    </bean>\r\n```\r\n##### component-scan扫描controller下的包\r\n\r\n```xml\r\n<!-- 4.扫描web相关的bean   这是spring特有注解扫描  让@Component生效 -->\r\n    <context:component-scan base-package=\"com.mx.controller\" />\r\n\r\n```\r\n\r\n\r\n## 末尾\r\n**配置Tomcat**\r\n## 务必\r\n**==​务必在structure里添加lib目录==\r\n​务必确认service层事务注解已经使用\r\n​全部import进入application**\r\n\r\n\r\n', '2020-11-14 16:10:02', 'http://qiniu.minxu.xyz/751ebe3d-f752-495e-9cfa-adf789a75384ssm.png', 0);
INSERT INTO `article` VALUES (4, 1, 3, 'ssm整合简化版', '# 固定配置\r\n## 对注解的理解\r\n#### 自动装配注解需要的config\r\n**context:annotation-config**\r\n1.如果想使用@ Resource 、@ PostConstruct、@ PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor。\r\n2.如果想使用@PersistenceContext注解，就必须声明PersistenceAnnotationBeanPostProcessor的Bean。\r\n3.如果想使用@Autowired注解，那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。\r\n4.如果想使用 @Required的注解，就必须声明RequiredAnnotationBeanPostProcessor的Bean。\r\n**而使用context:annotation-config/ 就可以隐式地自动向Spring容器注册4个BeanPostProcessor**\r\n\r\n#### 理解三个注解的扫描或驱动\r\n1. <context:annotation-config /> 作用隐式的配置注解的加载类，默认的加载了AutowiredAnnotationBeanPostProcessor（autowired） ,@Resource、@PostConstruct、@PreDestroy等\r\n2.<mvc:annotation-driven/>  将reques 请求上面的数据绑定到controller 上\r\n3.<context:component-scan base-package=\"com.xiao.*\" />  自动扫描某个包下带有注解的类，并进行注解的相关操作，@Autowired 注入bean; **另外也包含了1 中所做的操作**\r\n\r\n\r\n\r\n## pom依赖and资源扫描and编译器版本\r\n```xml\r\n<dependencies>\r\n  <!--lombok-->\r\n	<dependency>\r\n       <groupId>org.projectlombok</groupId>\r\n       <artifactId>lombok</artifactId>\r\n       <version>1.18.4</version>\r\n   </dependency>\r\n  <!--Junit-->\r\n  <dependency>\r\n      <groupId>junit</groupId>\r\n      <artifactId>junit</artifactId>\r\n      <version>4.12</version>\r\n  </dependency>\r\n  <!--数据库驱动-->\r\n  <dependency>\r\n      <groupId>mysql</groupId>\r\n      <artifactId>mysql-connector-java</artifactId>\r\n      <version>8.0.19</version>\r\n  </dependency>\r\n  <!-- 数据库连接池 可选-->\r\n\r\n  <!--Servlet - JSP -->\r\n  <dependency>\r\n      <groupId>javax.servlet</groupId>\r\n      <artifactId>servlet-api</artifactId>\r\n      <version>2.5</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>javax.servlet.jsp</groupId>\r\n      <artifactId>jsp-api</artifactId>\r\n      <version>2.2</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>javax.servlet</groupId>\r\n      <artifactId>jstl</artifactId>\r\n      <version>1.2</version>\r\n  </dependency>\r\n\r\n  <!--Mybatis-->\r\n  <dependency>\r\n      <groupId>org.mybatis</groupId>\r\n      <artifactId>mybatis</artifactId>\r\n      <version>3.5.2</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>org.mybatis</groupId>\r\n      <artifactId>mybatis-spring</artifactId>\r\n      <version>2.0.2</version>\r\n  </dependency>\r\n\r\n  <!--Spring-->\r\n  <dependency>\r\n      <groupId>org.springframework</groupId>\r\n      <artifactId>spring-webmvc</artifactId>\r\n      <version>5.1.9.RELEASE</version>\r\n  </dependency>\r\n  <dependency>\r\n      <groupId>org.springframework</groupId>\r\n      <artifactId>spring-jdbc</artifactId>\r\n      <version>5.1.9.RELEASE</version>\r\n  </dependency>\r\n</dependencies>\r\n\r\n<build>\r\n       <resources>\r\n           <resource>\r\n               <directory>src/main/java</directory>\r\n               <includes>\r\n                   <include>**/*.properties</include>\r\n                   <include>**/*.xml</include>\r\n               </includes>\r\n               <filtering>false</filtering>\r\n           </resource>\r\n           <resource>\r\n               <directory>src/main/resources</directory>\r\n               <includes>\r\n                   <include>**/*.properties</include>\r\n                   <include>**/*.xml</include>\r\n               </includes>\r\n               <filtering>false</filtering>\r\n           </resource>\r\n       </resources>\r\n       <plugins>\r\n           <plugin>\r\n               <groupId>org.apache.maven.plugins</groupId>\r\n               <artifactId>maven-compiler-plugin</artifactId>\r\n               <version>3.5</version>\r\n               <configuration>\r\n                   <source>1.8</source>\r\n                   <target>1.8</target>\r\n               </configuration>\r\n           </plugin>\r\n       </plugins>\r\n   </build>\r\n```\r\n\r\n## mybatis-config.xml\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n<!DOCTYPE configuration\r\n        PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\"\r\n        \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\r\n<configuration>\r\n    \r\n    <typeAliases>\r\n        <package name=\"com.mx.pojo\"/>\r\n    </typeAliases>\r\n\r\n</configuration>\r\n```\r\n## applicationContext.xml\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n       http://www.springframework.org/schema/beans/spring-beans.xsd\">\r\n\r\n    <import resource=\"spring-dao.xml\"/>\r\n    <import resource=\"spring-service.xml\"/>\r\n    <import resource=\"spring-mvc.xml\"/>\r\n</beans>\r\n```\r\n## database.properties\r\n\r\n```cpp\r\njdbc.driver=com.mysql.cj.jdbc.Driver\r\njdbc.url=jdbc:mysql://localhost:3306/db1?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai\r\njdbc.username=root\r\njdbc.password=root\r\n```\r\n\r\n## spring-dao.xml\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n       xmlns:context=\"http://www.springframework.org/schema/context\"\r\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd\">\r\n\r\n    <!--dao层如果 有mapple的实现类，则可以使用注解创建bean  在service层注入\r\n        不过已经可以动态实现  所以不需要扫描注解 -->\r\n\r\n\r\n    <!--1. 关联数据库文件-->\r\n    <context:property-placeholder location=\"classpath:database.properties\"/>\r\n\r\n    <!--2. 数据库连接池-->\r\n    <bean id=\"dataSource\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\">\r\n        <property name=\"driverClassName\" value=\"${jdbc.driver}\"/>\r\n        <property name=\"url\" value=\"${jdbc.url}\"/>\r\n        <property name=\"username\" value=\"${jdbc.username}\"/>\r\n        <property name=\"password\" value=\"${jdbc.password}\"/>\r\n    </bean>\r\n    <!--3. 注册sqlSessionFactory-->\r\n    <bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\">\r\n        <property name=\"dataSource\" ref=\"dataSource\"/>\r\n        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->\r\n        <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/>\r\n        <!-- 扫描model包 使用别名（此处在mybatis-config里取了别名） -->\r\n   		 <!-- <property name=\"typeAliasesPackage\" value=\"com.mx.pojo\"/>-->\r\n    </bean>\r\n\r\n\r\n    <!-- 4.配置扫描Dao接口包，动态实现Dao接口注入到spring容器中\r\n        即自动生成需要在service中注入的bean   在spring-service中注入进去\r\n    -->\r\n    <!--解释 ：https://www.cnblogs.com/jpfss/p/7799806.html-->\r\n    <bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\">\r\n        <property name=\"sqlSessionFactoryBeanName\" value=\"sqlSessionFactory\"/>\r\n        <!-- 给出需要扫描Dao接口包 -->\r\n        <property name=\"basePackage\" value=\"com.mx.mapper\"/>\r\n    </bean>\r\n\r\n</beans>\r\n```\r\n## spring-service.xml\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n       xmlns:context=\"http://www.springframework.org/schema/context\"\r\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd\">\r\n\r\n    <!--1. 扫描service层的注解 如果有的话-->\r\n    <context:component-scan base-package=\"com.mx.service\" />\r\n\r\n    <!--2. 注册***ServiceImpl的bean  注入***Mapper-->\r\n    <bean id=\"studentService\" class=\"com.mx.service.StudentServiceImpl\">\r\n        <property name=\"studentMapper\" ref=\"studentMapper\"/>\r\n    </bean>\r\n\r\n    <!-- 3. 配置事务管理器   在需要事务的方法上使用@Transi。。。-->\r\n        <tx:annotation-driven/>\r\n    <bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">\r\n        <property name=\"dataSource\" ref=\"dataSource\" />\r\n    </bean>\r\n</beans>\r\n```\r\n\r\n## spring-mvc.xml\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n       xmlns:context=\"http://www.springframework.org/schema/context\"\r\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd\">\r\n\r\n    <!-- 1.开启SpringMVC注解驱动 -->\r\n    <mvc:annotation-driven />\r\n    <!-- 2.静态资源默认servlet配置-->\r\n    <mvc:default-servlet-handler/>\r\n\r\n    <!-- 3.配置jsp 显示ViewResolver视图解析器 -->\r\n    <bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\r\n        <property name=\"viewClass\" value=\"org.springframework.web.servlet.view.JstlView\" />\r\n        <property name=\"prefix\" value=\"/WEB-INF/jsp/\" />\r\n        <property name=\"suffix\" value=\".jsp\" />\r\n    </bean>\r\n\r\n    <!-- 4.扫描web相关的bean -->\r\n    <context:component-scan base-package=\"com.mx.controller\" />\r\n</beans>\r\n```\r\n## web.xml\r\n\r\n```xml\r\n    <!--1. DispatcherServlet-->\r\n    <servlet>\r\n        <servlet-name>DispatcherServlet</servlet-name>\r\n        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\r\n        <init-param>\r\n            <param-name>contextConfigLocation</param-name>\r\n            <!--一定要注意:这里加载的是总的配置文件，之前被这里坑了！-->\r\n            <param-value>classpath:applicationContext.xml</param-value>\r\n        </init-param>\r\n        <load-on-startup>1</load-on-startup>\r\n    </servlet>\r\n    <servlet-mapping>\r\n        <servlet-name>DispatcherServlet</servlet-name>\r\n        <url-pattern>/</url-pattern>\r\n    </servlet-mapping>\r\n\r\n    <!--2. encodingFilter-->\r\n    <filter>\r\n        <filter-name>encodingFilter</filter-name>\r\n        <filter-class>\r\n            org.springframework.web.filter.CharacterEncodingFilter\r\n        </filter-class>\r\n        <init-param>\r\n            <param-name>encoding</param-name>\r\n            <param-value>utf-8</param-value>\r\n        </init-param>\r\n    </filter>\r\n    <filter-mapping>\r\n        <filter-name>encodingFilter</filter-name>\r\n        <url-pattern>/*</url-pattern>\r\n    </filter-mapping>\r\n\r\n    <!--3. Session过期时间-->\r\n    <session-config>\r\n        <session-timeout>15</session-timeout>\r\n    </session-config>\r\n```\r\n\r\n## 温馨提示\r\nmybatis-config.xml需要修改包名\r\ndatabase.properties需要修改数据库信息\r\nspring-dao.xml只需要修改最下面的包名\r\nspring-service.xml修改1和2的包名\r\nspring-mvc.xml修改最后的包名', '2020-11-14 16:11:02', 'http://qiniu.minxu.xyz/1f4cc753-276b-4053-9008-bf8feb852276ssm.png', 0);
INSERT INTO `article` VALUES (5, 1, 2, '富文本editor.md的集成', '# 富文本editor.md的集成\r\n\r\n## 下载editor.md\r\nhttps://github.com/pandao/editor.md/archive/master.zip\r\n\r\n## examples目录\r\n在这个目录中有许多的md集成案例可以借鉴  特别是simple.html \r\n\r\n## 简单的集成使用\r\n此处的style.css为examples/simple.html引用的style.css我复制出来了\r\n**故需要自行修改路径  引入相应名字的即可**\r\n\r\ncss引入：\r\n~~~html\r\n    <!--editor.md-->\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/style.css}\" />\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/editormd.css}\"/>\r\n~~~\r\njs引入：\r\n~~~html\r\n<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery，所以必须放在前边) -->\r\n<script src=\"https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js\"></script>\r\n<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->\r\n<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js\"></script>\r\n\r\n<script th:src=\"@{/lib/editormd/js/jquery.min.js}\"></script>\r\n<script th:src=\"@{/lib/editormd/editormd.min.js}\"></script>\r\n<script type=\"text/javascript\">\r\n    var testEditor;\r\n\r\n    $(function () {\r\n        testEditor = editormd(\"test-editormd\", {\r\n            width: \"100%\",\r\n            height: 600,\r\n            syncScrolling: \"single\",\r\n            path: \"../lib/editormd/lib/\"\r\n        });\r\n    });\r\n</script>\r\n~~~\r\n集成截图\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915204136702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n\r\n## 实现md转html显示\r\n前端代码：\r\n~~~html\r\n<!DOCTYPE html>\r\n<html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>Title</title>\r\n\r\n    <!--editor.md-->\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/style.css}\" />\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/editormd.css}\"/>\r\n\r\n</head>\r\n<body>\r\n<div id=\"test-editormd-view\">\r\n    <textarea style=\"display:none;\" th:text=\"${content}\" name=\"test-editormd-markdown-doc\"></textarea>\r\n</div>\r\n\r\n<script src=\"https://cdn.bootcss.com/jquery/2.2.3/jquery.min.js\"></script>\r\n<script th:src=\"@{/editormd/lib/marked.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/prettify.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/raphael.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/underscore.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/sequence-diagram.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/flowchart.min.js}\"></script>\r\n<script th:src=\"@{/editormd/lib/jquery.flowchart.min.js}\"></script>\r\n<script th:src=\"@{/editormd/editormd.js}\"></script>\r\n<script type=\"text/javascript\">\r\n    testEditormdView2 = editormd.markdownToHTML(\"test-editormd-view\", {\r\n        htmlDecode      : \"style,script,iframe\",  // you can filter tags decode\r\n        emoji           : true,\r\n        taskList        : true,\r\n        tex             : true,  // 默认不解析\r\n        flowChart       : true,  // 默认不解析\r\n        sequenceDiagram : true,  // 默认不解析\r\n    });\r\n</script>\r\n\r\n</body>\r\n</html>\r\n~~~\r\n**实现效果：**\r\n自动附带代码高亮效果\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915204318101.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n## 实现图片上传功能\r\n上传配置\r\n~~~yml\r\n  servlet:\r\n    multipart:\r\n      max-file-size: 10MB\r\n      max-request-size: 10MB\r\n~~~\r\n\r\n前端代码\r\n~~~html\r\n	<!DOCTYPE html>\r\n<html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n    <!-- 上述3个meta标签*必须*放在最前面，任何其他内容都*必须*跟随其后！ -->\r\n\r\n    <!--editor.md-->\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/style.css}\" />\r\n    <link rel=\"stylesheet\" th:href=\"@{/lib/editormd.css}\"/>\r\n\r\n    <!-- Bootstrap -->\r\n    <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\r\n\r\n    <link rel=\"stylesheet\" th:href=\"@{/css/me.css}\">\r\n    <title>后台</title>\r\n</head>\r\n<body>\r\n\r\n<div th:replace=\"~{commons::admin(\'blog\')}\"></div>\r\n\r\n<div class=\"container\">\r\n    <!--身体-->\r\n    <div class=\"container\" style=\"opacity: 0.9;\">\r\n        <form th:action=\"@{/admin/addArticle}\" method=\"post\" enctype=\"multipart/form-data\">\r\n            <div class=\"row text-left\" style=\"display: flex\">\r\n                <label>文章标题:</label>\r\n                <input name=\"title\" style=\"width: 25em\" class=\"form-control\"\r\n                       required placeholder=\"请输入标题...\" type=\"text\">\r\n                <label>封面图片:</label>\r\n                <input type=\"file\" name=\"file\">\r\n            </div>\r\n            <div class=\"row text-left\">\r\n                <label>选择标签:</label>\r\n                <label th:each=\"tag:${tags}\" class=\"checkbox-inline\">\r\n                    <input name=\"tags\" type=\"checkbox\" th:value=\"${tag.getId()}\"> <p th:text=\"${tag.getName()}\"></p>\r\n                </label>\r\n            </div>\r\n            <div class=\"row\">\r\n                <div id=\"layout\">\r\n                    <header>\r\n                        <h2>编辑博客</h2>\r\n                    </header>\r\n                    <div id=\"test-editormd\">\r\n                <textarea name=\"content\">[TOC]\r\n                #### Disabled options\r\n\r\n                - TeX (Based on KaTeX);\r\n                - Emoji;\r\n                - Task lists;\r\n                - HTML tags decode;\r\n                - Flowchart and Sequence Diagram;\r\n                </textarea>\r\n                    </div>\r\n                </div>\r\n            </div>\r\n            <div class=\"row text-right\">\r\n                <input type=\"submit\" class=\"btn btn-success\" style=\"width: 5em\">\r\n            </div>\r\n        </form>\r\n    </div>\r\n\r\n</div>\r\n\r\n\r\n\r\n<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery，所以必须放在前边) -->\r\n<script src=\"https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js\"></script>\r\n<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->\r\n<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js\"></script>\r\n\r\n<script th:src=\"@{/lib/editormd.min.js}\"></script>\r\n<script type=\"text/javascript\">\r\n    var testEditor;\r\n\r\n    $(function() {\r\n        testEditor = editormd(\"test-editormd\", {\r\n            width   : \"90%\",\r\n            height  : 600,\r\n            syncScrolling : \"single\",\r\n            //图片上传\r\n            imageUpload : true,\r\n            imageFormats : [\"jpg\", \"jpeg\", \"gif\", \"png\", \"bmp\", \"webp\"],\r\n            imageUploadURL : \"/admin/upload\",\r\n            onload : function() {\r\n                console.log(\'onload\', this);\r\n            },\r\n            path    : \"/editormd/lib/\"\r\n        });\r\n\r\n\r\n    });\r\n</script>\r\n\r\n</body>\r\n</html>\r\n~~~\r\n\r\n\r\n后端接收图片  ==返回格式必须一致==\r\n\r\n~~~java\r\n@RequestMapping(\"/upload\")\r\n    @ResponseBody\r\n    public HashMap<String, Object> upload(@RequestParam(\"editormd-image-file\") MultipartFile pic,Model model){\r\n        System.out.println(\"进入upload方法\");\r\n        String downloadPath=\"\";\r\n        if(pic.isEmpty()){\r\n            System.err.println(\"上传的图片为空\");\r\n        }\r\n        else{\r\n            // ResourceUtils.getURL(\"classpath:\") 是获取当前文件的resources路径\r\n            try {\r\n                // 获取文件的原始名称\r\n                String oldFileName = pic.getOriginalFilename();\r\n                String realPath = ResourceUtils.getURL(\"classpath:\").getPath() + \"static/files\";\r\n                // 处理文件上传\r\n                realPath=realPath.substring(1);\r\n                File file = new File(realPath);\r\n                if(!file.exists())\r\n                    file.mkdir();\r\n                System.out.println(realPath);\r\n                pic.transferTo(new File(realPath,oldFileName));\r\n                downloadPath=\"http://localhost:8084/files/\"+oldFileName;\r\n                System.out.println(\"downloadPath:http://localhost:8084/files/\"+oldFileName);\r\n            } catch (FileNotFoundException e) {\r\n                e.printStackTrace();\r\n            } catch (IOException e) {\r\n                e.printStackTrace();\r\n            }\r\n        }\r\n        HashMap<String, Object> map = new HashMap<>();\r\n        //必须返回此格式\r\n        map.put(\"url\",downloadPath);\r\n        map.put(\"success\", 1);\r\n        map.put(\"message\", \"upload success!\");\r\n\r\n        return map;\r\n    }\r\n   ~~~\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200928000115941.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n', '2020-11-14 16:13:34', 'http://qiniu.minxu.xyz/8de08749-4c90-4ece-a5b6-d973de87bd08editor.png', 0);
INSERT INTO `article` VALUES (6, 1, 2, 'druid集成于springboot', '# druid集成于springboot\r\n\r\n## 导入依赖\r\n~~~xml\r\n       <!--druid-->\r\n       <dependency>\r\n           <groupId>com.alibaba</groupId>\r\n           <artifactId>druid-spring-boot-starter</artifactId>\r\n           <version>1.1.21</version>\r\n       </dependency>\r\n~~~\r\n\r\n## yml中配置\r\n~~~yml\r\nspring:\r\n  datasource:\r\n    driver-class-name: com.mysql.cj.jdbc.Driver\r\n    url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC\r\n    username: blog\r\n    password: xxxxx\r\n    type: com.alibaba.druid.pool.DruidDataSource\r\n    druid:\r\n      initialSize: 5\r\n      minIdle: 5\r\n      maxActive: 20\r\n      maxWait: 60000\r\n      timeBetweenEvictionRunsMillis: 60000\r\n      minEvictableIdleTimeMillis: 300000\r\n      validationQuery: SELECT 1 FROM DUAL\r\n      testWhileIdle: true\r\n      testOnBorrow: false\r\n      testOnReturn: false\r\n      poolPreparedStatements: true\r\n      #   配置监控统计拦截的filters，去掉后监控界面sql无法统计，\'wall\'用于防火墙\r\n      filters: stat,wall,log4j\r\n      maxPoolPreparedStatementPerConnectionSize: 20\r\n      useGlobalDataSourceStat: true\r\n      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500\r\n\r\nserver:\r\n  port: 8083\r\n\r\nmybatis-plus:\r\n  type-aliases-package: com.mx.pojo\r\n  mapper-locations: classpath:mybatis/mapper/*.xml\r\n~~~\r\n\r\n## 配置监控功能\r\n编写DruidConfig 配置类\r\n~~~java\r\npackage com.mx.config;\r\n\r\nimport com.alibaba.druid.pool.DruidDataSource;\r\nimport com.alibaba.druid.support.http.StatViewServlet;\r\nimport com.alibaba.druid.support.http.WebStatFilter;\r\nimport org.springframework.boot.context.properties.ConfigurationProperties;\r\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\r\nimport org.springframework.boot.web.servlet.ServletRegistrationBean;\r\nimport org.springframework.context.annotation.Bean;\r\nimport org.springframework.context.annotation.Configuration;\r\n\r\nimport javax.sql.DataSource;\r\nimport java.util.Arrays;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\n@Configuration\r\npublic class DruidConfig {\r\n\r\n    @ConfigurationProperties(prefix = \"spring.datasource\")\r\n    @Bean\r\n    public DataSource druid(){\r\n        return  new DruidDataSource();\r\n    }\r\n\r\n    //配置Druid的监控\r\n    //1、配置一个管理后台的Servlet\r\n    @Bean\r\n    public ServletRegistrationBean statViewServlet(){\r\n        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), \"/druid/*\");\r\n        Map<String,String> initParams = new HashMap<>();\r\n\r\n        initParams.put(\"loginUsername\",\"admin\");\r\n        initParams.put(\"loginPassword\",\"123456\");\r\n        initParams.put(\"allow\",\"\");//默认就是允许所有访问\r\n        initParams.put(\"deny\",\"192.168.15.21\");//禁止某人访问\r\n\r\n        bean.setInitParameters(initParams);\r\n        return bean;\r\n    }\r\n\r\n\r\n    //2、配置一个web监控的filter\r\n    @Bean\r\n    public FilterRegistrationBean webStatFilter(){\r\n        FilterRegistrationBean bean = new FilterRegistrationBean();\r\n        bean.setFilter(new WebStatFilter());\r\n\r\n        Map<String,String> initParams = new HashMap<>();\r\n        initParams.put(\"exclusions\",\"*.js,*.css,/druid/*\");\r\n\r\n        bean.setInitParameters(initParams);\r\n\r\n        bean.setUrlPatterns(Arrays.asList(\"/*\"));\r\n\r\n        return  bean;\r\n    }\r\n}\r\n\r\n~~~\r\n### 使用监控功能\r\n访问http://localhost:8083/druid\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915204933590.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n大功告成\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2020091520503464.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n', '2020-11-14 16:15:15', NULL, 0);
INSERT INTO `article` VALUES (7, 1, 4, '文件上传springboot', '# 文件上传springboot\r\n前端\r\n~~~html\r\n<!DOCTYPE html>\r\n<html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>Title</title>\r\n</head>\r\n<body>\r\n<form th:action=\"@{/test1}\" method=\"post\" enctype=\"multipart/form-data\">\r\n    <input type=\"text\" name=\"name\">\r\n    <input type=\"text\" name=\"username\">\r\n    <input type=\"file\" name=\"file\">\r\n    <input type=\"submit\">\r\n</form>\r\n</body>\r\n</html>\r\n~~~\r\n\r\n\r\n后端\r\n\r\n~~~java\r\n  @RequestMapping(\"/test1\")\r\n    public String test1(User user, @RequestParam(\"file\") MultipartFile pic) {\r\n        System.out.println(\"接收到的\" + user);\r\n        System.out.println(\"进入了test方法\");\r\n\r\n\r\n        // ResourceUtils.getURL(\"classpath:\") 是获取当前文件的resources路径\r\n        try {\r\n            // 获取文件的原始名称\r\n            String oldFileName = pic.getOriginalFilename();\r\n            String realPath = ResourceUtils.getURL(\"classpath:\").getPath() + \"static/files\";\r\n            // 处理文件上传\r\n            realPath=realPath.substring(1);\r\n            File file = new File(realPath);\r\n            System.out.println(file.exists());\r\n            if(!file.exists())\r\n                file.mkdir();\r\n            System.out.println(realPath);\r\n            pic.transferTo(new File(realPath,oldFileName));\r\n            System.out.println(\"/files/\"+oldFileName);\r\n\r\n        } catch (FileNotFoundException e) {\r\n            e.printStackTrace();\r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        }\r\n\r\n        return \"redirect:/test\";\r\n    }\r\n~~~', '2020-11-14 16:15:41', NULL, 0);
INSERT INTO `article` VALUES (8, 1, 8, 'docker学习', '# docker安装常用软件\r\n### 安装mysql\r\n![](http://qiniu.minxu.xyz/51764efa-3dee-45d0-a1c6-33bfc6be78ddaQQ截图20201115002025.png)\r\n![](http://qiniu.minxu.xyz/609c8423-69d5-41c2-bb16-b629598b5e77aa20201115002140.png)\r\n\r\n~~~cmd\r\ndocker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:latest\r\n	#  -d 表示后台运行\r\n	#  -p 3306:3306 表示端口映射   不加只能内部访问 \r\n	# -e MYSQL_ROOT_PASSWORD=xxxxx 必须设定-e参数给定mysql的root密码\r\n	# --name xxxx   可以给定容器的名称（可选）\r\n	# 最后指定mysql   可以指定版本也可以不指定\r\n~~~\r\n### 安装redis\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916173123702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n### 安装nginx\r\n拷贝出来配置文件   运行时可以挂载进去\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916220324550.png#pic_center)\r\n需要自行匹配挂载目录\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916220609291.png#pic_center)\r\n\r\n# docker命令学习\r\ntips：镜像运行产生容器\r\n\r\n==centos查看防火墙状态==\r\n~~~cmd\r\nsystemctl status firewalld\r\n~~~\r\n==开启、重启、关闭、firewalld.service服务==\r\n~~~cmd\r\n# 开启\r\nservice firewalld start\r\n# 重启\r\nservice firewalld restart\r\n# 关闭\r\nservice firewalld stop\r\n~~~\r\n### 基础操作\r\n==docker启动命令==\r\n~~~cmd\r\nsystemctl start docker\r\n~~~\r\ntips：dockerhub是类似maven repository的  可以查看docker能下载的镜像\r\n\r\n==docker是cs架构   我们配置的都是客户端  服务端理应一直开启  此命令可以查看==\r\n~~~cmd\r\ndocker info\r\n~~~\r\n==查看有哪些镜像==\r\n~~~cmd\r\nsystemctl images									列出运行的镜像的信息\r\n	systemctl images -q							只列出运行的镜像的id\r\n	systemctl images -a							列出所有镜像（包括运行过的）\r\n	systemctl images -aq							所有运行过的id\r\n~~~\r\n==去dockerhub搜索需要下载的镜像==\r\n~~~cmd\r\ndocker search xxx\r\n~~~\r\n==下载镜像==\r\n~~~cmd\r\ndocker pull xxx\r\n	docker pull xxx:latest    					下载最新的版本\r\n	docker pull xxx:版本号   					下载相应的版本\r\n~~~\r\n==删除镜像==\r\n~~~cmd\r\ndocker rmi xxx\r\n	docker rmi xxx:版本号   					下载相应的版本\r\n	docker rmi xxx -f   							强制删除\r\n~~~\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916132455468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n\r\n\r\n### 容器操作\r\n==镜像运行就叫容器==\r\ndocker运行之后的容器相当与一台封装的虚拟机，需要内外端口映射\r\n~~~cmd\r\ndocker run -d -p 外部端口:内部端口 镜像名\r\n	-d的含义是守护进程运行  否则会占用当前终端\r\n	docker run -d --name 取的名字 -p 外部端口:内部端口 镜像名\r\n\r\n	docker run -p 外部端口:内部端口 镜像名\r\n		一般不这么运行:    docker run 镜像名    \r\n			提前一步docker images即可获得全部镜像名\r\n~~~\r\n==开启 关闭 重启 容器==\r\n~~~cmd\r\ndocker start	容器名或容器id   		id的前几个数字也可识别\r\ndocker stop 容器名或容器id   		id的前几个数字也可识别\r\ndocker restart 容器名或容器id   		id的前几个数字也可识别\r\n\r\n不建议 会丢失数据：\r\ndocker kill 容器名或容器id   		id的前几个数字也可识别\r\n~~~\r\n\r\n==查询运行的镜像==\r\n~~~cmd\r\ndocker ps\r\n~~~\r\n\r\n### docker进阶命令（背诵）\r\n> 进入容器内部终端\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916133118854.png#pic_center)\r\n-it下面有解释，/bin/bash 表示当前终端   必须加上才能进行交互\r\n\r\n##### 镜像内部是ubuntu  如果需要安装东西 比如vi 需要：\r\n~~~cmd\r\n# 更新apt-get\r\napt-get upodate\r\n# 下载vim   因为该命令下载不了vi\r\napt-get install vim\r\n~~~\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200916132849494.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n\r\n# docker安装\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915234534260.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n# docker的原理\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915235041191.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915234951256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n# docker的阿里云加速服务\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915235240681.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70#pic_center)\r\n执行右下的命令即可', '2020-11-14 16:21:59', 'http://qiniu.minxu.xyz/bfa0a869-4108-482f-a3fd-138c4e89ef68dockerQQ截图20201115001628.png', 0);
INSERT INTO `article` VALUES (9, 1, 5, 'springboot项目部署', '# springboot项目部署\r\n1. 打包项目\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930191257635.png#pic_center)\r\n\r\n2.服务器的宝塔上务必设置数据库所有人访问\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930192048698.png#pic_center)\r\n3. 上传jar包到服务器\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930192340794.png#pic_center)\r\n\r\n4. 运行命令\r\n\r\n```\r\nnohup java -jar XXX.jar > Log.log 2>&1 & \r\n或者\r\nnohup java -jar XXX.jar >> Log.log 2>&1 & \r\n或者\r\nnohup java -jar XXX.jar > /dev/null 2>&1 & \r\n\r\n	# 查看情况\r\n	ps -ef|grep jar\r\n	# 停止jar\r\n	kill -9 -xxx\r\n\r\n```\r\n==命令说明==\r\n- 命令 \"nohup java -jar XXX.jar &\" 部分，表示不挂断运行命令,当账户退出或终端关闭时,程序仍然运行。注意，该作业的所有输出被重定向到nohup.out的文件中。\r\n- 命令 \"nohup java -jar XXX.jar > Log.log &\" 部分，表示不挂断运行命令,当账户退出或终端关闭时,程序仍然运行，并且该作业的所有输出被重定向到Log.log的文件中。“ > Log.log ” 该命令就是指定日志输出的文件。\r\n- \">>\"表示将输出以追加的方式重定向到Log.log中。\r\n\r\n- 标准输入文件(stdin)：stdin的文件描述符为0，Unix程序默认从stdin读取数据。\r\n- 标准输出文件(stdout)：stdout 的文件描述符为1，Unix程序默认向stdout输出数据。\r\n- 标准错误文件(stderr)：stderr的文件描述符为2，Unix程序会向stderr流中写入错误信息。\r\n- 屏蔽输出，起到禁止输出作用：/dev/null 是一个特殊的文件，写入到它的内容都会被丢弃；如果尝试从该文件读取内容，那么什么也读不到。但是 /dev/null 文件非常有用，将命令的输出重定向到它，会起到\"禁止输出\"的效果。\r\n- “> Log.log 2>&1” ：表示将 stdout 和 stderr 合并后重定向到 Log.log', '2020-11-14 16:18:37', 'http://qiniu.minxu.xyz/59a33a26-8963-4cbb-aa5e-2afc16c24ea3aaaa01115001821.png', 0);
INSERT INTO `article` VALUES (11, 2, 0, 'JDK源码学习01-HashMap源码学习', '# HashMap源码学习\r\nHashMap中直接注意的细节：\r\n1. 红黑树长度小于阈值(yu 4声)  `6`  转化成两边\r\n2. 链表长度大于阈值8 且table的长度大于等于64  才树化  仅满足链表长度大于阈值8只会调用resize扩容\r\n   \r\n\r\n```\r\n为什么是6和8呢，而不设置成一样呢，因为为了防止在边界反复横跳，浪费性能\r\n```\r\n\r\n```\r\n if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)\r\n            resize();\r\n```\r\n\r\n3. HashMap扩容时数据迁移\r\n**a. 旧表槽为空  直接跳过\r\nb. 旧表槽中仅一个节点   求得新的索引再放入**\r\n newTab[e.hash & (newCap - 1)] = e;\r\n**c. 旧表中是红黑树**\r\n红黑树中其实依旧维持了链表结构  故处理方法和链标一样\r\n**d. 旧表中是链标**\r\n```\r\n	首先  e.hash是不变的  这里设oldCap老表长度为16\r\n	则老表的下标计算只是用了e.hash的低4位   已知e.hash&15=j\r\n	由于表扩容了一倍 所以现在  e.hash的低5位都应该被使用\r\n	而已经有了底4位使用的记录  如果e.hash的第五位是0  则数据所在位置和老表一样\r\n	如果e.hash的第五位是1   则位置为j+oldCap即j+newTab.length/2\r\n	因为如果e.hash的第五位是1  那么数据必然会存储在新表的后半段\r\n	而低四位的值就相当于offset这里是j   就就是存储位置相对于newTab.length/2的偏差\r\n```\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210411200157662.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n\r\n### 静态成员\r\n```\r\n# 默认初始化容量，在第一次put时table才会初始化为该容量\r\n# 如果new HashMap(n)  指定了长度  则直接table为该容量\r\n    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16\r\n    \r\n# table最大容量2^30次方\r\n    static final int MAXIMUM_CAPACITY = 1 << 30;\r\n    \r\n# 加载因子  当size>DEFAULT_LOAD_FACTOR*CAPACITY时会扩容\r\n    static final float DEFAULT_LOAD_FACTOR = 0.75f;\r\n\r\n# 树化阈值  链表长度大于阈值8 且table的长度大于64才树化  仅满足链表长度大于阈值8只会调用resize扩容\r\n    static final int TREEIFY_THRESHOLD = 8;//大于8  切table长度大于64\r\n# 最小树化容量   必须同时满足链表大于8   且长度大于64才能树化   只满足大于8就会扩容\r\n    static final int MIN_TREEIFY_CAPACITY = 64;\r\n\r\n# 取消树化阈值  当树的长度小于6的时候  会退化成链表  \r\n	static final int UNTREEIFY_THRESHOLD = 6;//小于6\r\n```\r\n### 成员变量\r\n\r\n```\r\n# hashmap的哈希表\r\n    transient Node<K,V>[] table;\r\n    \r\n# 保存缓存的entrySet()。请注意，抽象映射字段用于keySet()和values()。\r\n    transient Set<Map.Entry<K,V>> entrySet;\r\n    \r\n# 当前大小\r\n    transient int size;\r\n\r\n# 表结构修改次数  此字段用于使HashMap集合视图上的迭代器快速失败\r\n    transient int modCount;\r\n    \r\n# 下次需要扩容的大小 (capacity * load factor).  当size>threshold且table长度大于64才扩容\r\n    int threshold;\r\n    \r\n# 实际的加载因子  可以在new HashMap(n,loadFactor)的时候指定\r\n    final float loadFactor;\r\n        public HashMap(int initialCapacity, float loadFactor)\r\n        this.loadFactor = loadFactor;\r\n        this.threshold = tableSizeFor(initialCapacity);\r\n```\r\n\r\n\r\n\r\n## hash函数与tableSizeFor函数（静态工具函数）\r\nhashcode生成的是32位的码，为了完整使用到32位，并更好的散列，就将高16位与低16位异或（如果相同则为0，如果不同则为1）\r\n【原因：使用 | 会出现更多1  使用 & 会出现更多0    故都不合适】\r\n```java\r\n    static final int hash(Object key) {\r\n        int h;\r\n        //  >>>表示右移后左边补0    所以就是32位h的高16位和低16位\r\n        //  ^是异或  00 11为1    01 10为0\r\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\r\n    }\r\n```\r\n> 这里我们使用一个demo来测试一下hash函数，我们可以发现，hash是在hashcode的基础上，把高16位保持不变，第十六位变成【高十六位和低16位异或】，保证了在不太大的容量的情况下更好的散列性\r\n\r\n```java\r\npublic static void main(String[] args) {\r\n        Test2 test2 = new Test2();\r\n        int hashCode = test2.hashCode();\r\n        int hash = hash(test2);\r\n        System.out.println(hashCode);\r\n        System.out.println(hash);\r\n\r\n        System.out.println(Integer.toBinaryString(hashCode));\r\n        System.out.println(Integer.toBinaryString(hash));\r\n    }\r\n```\r\n输出：\r\n```\r\n692404036\r\n692393473\r\n101001010001010011111101000100\r\n101001010001010001011000000001\r\n```\r\n\r\n\r\ntableSizeFor的功能（不考虑大于最大容量的情况）是返回大于输入参数且最近的2的整数次幂的数。\r\n```java\r\n    static final int tableSizeFor(int cap) {\r\n        int n = cap - 1;\r\n        n |= n >>> 1;\r\n        n |= n >>> 2;\r\n        n |= n >>> 4;\r\n        n |= n >>> 8;\r\n        n |= n >>> 16;\r\n        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\r\n    }\r\n```\r\n\r\n\r\n## put函数\r\n通过put函数  我们能很好的认识到HashMap的结构\r\n```java\r\n    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,\r\n                   boolean evict) {\r\n        Node<K,V>[] tab; Node<K,V> p; int n, i;\r\n        //懒加载   仅仅new  HashTable()  里面啥也没有   此处resize创建默认长度16\r\n        if ((tab = table) == null || (n = tab.length) == 0)\r\n            n = (tab = resize()).length;\r\n\r\n        if ((p = tab[i = (n - 1) & hash]) == null)//如果这个桶是空的  创建桶的第一个节点\r\n            tab[i] = newNode(hash, key, value, null);\r\n        else {\r\n            //Node<K,V>这个结构就表示节点和桶首节点   e用来存储旧数据    p是当前桶的首节点\r\n            Node<K,V> e; K k;\r\n\r\n            /* 此时获得一个由hash值相同的元素组成的链表  链表的首节点是p\r\n            * 可能出现两种情况\r\n            *      ==p是树节点和p不是树节点==\r\n            * 树：putTreeVal  红黑树插入之后返回旧数据  如果没有返回空\r\n            * 链表：我们循环检查链表中的value和key是否equals\r\n            *   遍历到尾部就会添加新节点  返回的e就为null\r\n            *   插入新节点  并判断是否达到TREEIFY_THRESHOLD值\r\n            *   if-else用来判断三种情况\r\n            *   1. 判断p就是要找的节点\r\n            *   2. 判断p是树节点，则调用putTreeVal方法，如果有旧数据就返回给e，否则返回null\r\n            *   3. 此时只剩下检索链表\r\n            */\r\n\r\n            if (p.hash == hash &&\r\n                ((k = p.key) == key || (key != null && key.equals(k))))\r\n                e = p;//e指向旧节点\r\n            else if (p instanceof TreeNode)\r\n                //红黑树插入之后返回旧数据  如果没有返回空\r\n                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\r\n            else {\r\n                for (int binCount = 0; ; ++binCount) {\r\n                    /*\r\n                    * binCount检索的是 p为首节点的第 length=bincount+2个节点 \r\n                    * 所以下面判断的是实际节点数length>=9\r\n                    * 比如bincount为0检索的是p.next  p是节点1   p.next就是节点2   依次类推\r\n                    * */\r\n\r\n                    //如果检索到链表末尾 则插入节点  并判断是否树化\r\n                    if ((e = p.next) == null) {\r\n                        p.next = newNode(hash, key, value, null);\r\n                        //如果长度大于等于9  则转变成二叉树 （注：此时两边同时加2  左边是节点数  右边是9）\r\n                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\r\n                            treeifyBin(tab, hash);\r\n                        break;\r\n                    }\r\n                    //如果e和当前节点的key相等   则break   e记录旧节点的值\r\n                    if (e.hash == hash &&\r\n                        ((k = e.key) == key || (key != null && key.equals(k))))\r\n                        break;\r\n                    p = e;\r\n                }\r\n            }\r\n            //如果存在旧节点  返回旧值  并更新旧节点的值\r\n            if (e != null) { // existing mapping for key\r\n                V oldValue = e.value;//记录旧值\r\n                if (!onlyIfAbsent || oldValue == null)\r\n                    e.value = value;//赋值新值\r\n                afterNodeAccess(e);\r\n                return oldValue;\r\n            }\r\n        }\r\n        ++modCount;\r\n        //大于负载因子*容量的时候才扩容   故初始时put第13个扩容\r\n        if (++size > threshold)//The next size value at which to resize (capacity * load factor).\r\n            resize();\r\n        afterNodeInsertion(evict);\r\n        return null;\r\n    }\r\n```\r\n\r\n## resize函数\r\n\r\n```java\r\nfinal Node<K,V>[] resize() {\r\n        Node<K,V>[] oldTab = table;\r\n        int oldCap = (oldTab == null) ? 0 : oldTab.length;\r\n        int oldThr = threshold;\r\n        int newCap, newThr = 0;\r\n        if (oldCap > 0) {\r\n            if (oldCap >= MAXIMUM_CAPACITY) {\r\n                threshold = Integer.MAX_VALUE;\r\n                return oldTab;\r\n            }\r\n            //newCap=2*oldCap  扩容到原来的两倍大小\r\n            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\r\n                     oldCap >= DEFAULT_INITIAL_CAPACITY)\r\n                newThr = oldThr << 1; // double threshold\r\n        }\r\n        else if (oldThr > 0) // initial capacity was placed in threshold\r\n            //当使用new HashTable(n)给定了长度  n先被转化成2的幂  然后new Node[newCap]出table的长度\r\n            newCap = oldThr;\r\n        else {               // zero initial threshold signifies using defaults\r\n            //懒加载的实现  在第一次put元素时  创建默认长度的table\r\n            newCap = DEFAULT_INITIAL_CAPACITY;\r\n            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\r\n        }\r\n        //设置下次扩容的阈值capacity*loadFactor\r\n        if (newThr == 0) {\r\n            float ft = (float)newCap * loadFactor;\r\n            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\r\n                      (int)ft : Integer.MAX_VALUE);\r\n        }\r\n        threshold = newThr;\r\n        @SuppressWarnings({\"rawtypes\",\"unchecked\"})\r\n        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];\r\n        table = newTab;\r\n        if (oldTab != null) {//扩容时老表不为null  如果为空跳过该片段 直接返回新建的newTab\r\n\r\n            //此时扩容后  元素会重新迁移   在老表中的位置不代表新表中的位置\r\n            for (int j = 0; j < oldCap; ++j) {\r\n                Node<K,V> e;\r\n                //遍历每个桶 只有桶中有数据才需要迁移\r\n                if ((e = oldTab[j]) != null) {\r\n                    oldTab[j] = null;\r\n\r\n                    //如果该节点没有连接链表和红黑树\r\n                    if (e.next == null)\r\n                        newTab[e.hash & (newCap - 1)] = e;\r\n                    //如果连接红黑树  这里红黑树额外维系了链表结构\r\n                    //采取措施和链表类似  使用高低链表\r\n                    else if (e instanceof TreeNode)\r\n                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);\r\n                    //连接链表\r\n                    else { // preserve order\r\n                        Node<K,V> loHead = null, loTail = null;\r\n                        Node<K,V> hiHead = null, hiTail = null;\r\n                        Node<K,V> next;\r\n                        do {\r\n                            next = e.next;\r\n                            /*\r\n                            * 这里非常巧妙\r\n                            * 首先  e.hash是不变的  这里设oldCap老表长度为16\r\n                            * 则老表的下标计算只是用了e.hash的低4位   已知e.hash&15=j\r\n                            * 由于表扩容了一倍 所以现在  e.hash的低5位都应该被使用\r\n                            * 而已经有了底4位使用的记录  如果e.hash的第五位是0  则数据所在位置和老表一样\r\n                            * 如果e.hash的第五位是1   则位置为j+oldCap即j+newTab.length/2\r\n                            * 因为如果e.hash的第五位是1  那么数据必然会存储在新表的后半段\r\n                            * 而低四位的值就相当于offset这里是j   就就是存储位置相对于newTab.length/2的偏差\r\n                             * */\r\n                            if ((e.hash & oldCap) == 0) {\r\n                                if (loTail == null)\r\n                                    loHead = e;\r\n                                else\r\n                                    loTail.next = e;\r\n                                loTail = e;\r\n                            }\r\n                            else {\r\n                                if (hiTail == null)\r\n                                    hiHead = e;\r\n                                else\r\n                                    hiTail.next = e;\r\n                                hiTail = e;\r\n                            }\r\n                        } while ((e = next) != null);\r\n                        if (loTail != null) {\r\n                            loTail.next = null;\r\n                            newTab[j] = loHead;\r\n                        }\r\n                        if (hiTail != null) {\r\n                            hiTail.next = null;\r\n                            newTab[j + oldCap] = hiHead;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return newTab;\r\n    }\r\n\r\n```\r\n', '2021-11-18 09:28:47', 'http://znbiscool.icu/19a65cf0-638b-4ef5-bc99-ae1b90f8f4a601ae2958ae3c46a801219c774d7171.jpg@900w_1l_2o_100.jpg', 0);
INSERT INTO `article` VALUES (12, 2, 0, 'JDK源码学习02-ArrayList源码和简单实现', '# ArrayList源码学习\r\n## 简介\r\n> 简单实现见文章底部\r\n\r\nArrayList是常用的集合，ArrayList查询效率高，增删效率低\r\n自己实现ArrayList最需要注意的细节：\r\n1. 加法优先级高于位运算，故必须要加括号才行\r\n```java\r\n	int oneAndHalf = elementData.length + (elementData.length >> 1);\r\n```\r\n2. 删除元素，往前移动index后的元素，移动之后，最后一个元素要置位null，否则会产生内存泄漏，比较容易产生内存泄漏的还有threadlocal   \r\n```java\r\n public E remove(int index) {\r\n        checkIndex(index);\r\n        E t=(E)elementData[index];\r\n        System.arraycopy(elementData,index+1,elementData,index,size-index-1);\r\n        //设置为空 以免产生内存溢出\r\n        elementData[size-1]=null;\r\n        size--;\r\n        return t;\r\n    }\r\n```\r\n3. 查询元素和判断元素相等的时候，需要把null值额外判断一遍\r\nremove(Object o)\r\nindexOf(Object o)\r\n\r\n\r\n\r\n**复制数组的重要工具类**\r\n```\r\n# 最常用最重要   特别是原数组与目标数组为同一个数组的时候\r\nSystem.arraycopy(原数组, 原数组开始索引, 目标数组,目标数组开始索引,\r\n                       复制长度);\r\n\r\n# 拷贝原数组前newLength个元素生成一个新数组 \r\npublic static T[] Arrays.copyOf(T[] original, int newLength)\r\n\r\n# 拷贝original的索引为[form,to)返回一个新数组\r\npublic static <T> T[] copyOfRange(T[] original,\r\n                                  int from,\r\n                                  int to)\r\n```\r\n\r\n### 静态成员\r\n> 没有特别意义的两个常量:    但是由于引用的地址不同，可以区别懒加载还是初始化容量0\r\n1. `EMPTY_ELEMENTDATA` 在new ArrayList(int initialCapacity)  指定长度为0时使用，\r\n在其他任意表示elementData 为空时也会使用\r\n2. `DEFAULTCAPACITY_EMPTY_ELEMENTDATA `仅在new ArrayList() 没有指定长度时才会使用\r\n```\r\n# 默认初始容量  但是会使用懒加载\r\nprivate static final int DEFAULT_CAPACITY = 10;\r\n\r\n#  new ArrayList(int initialCapacity)   指定长度为0时\r\nprivate static final Object[] EMPTY_ELEMENTDATA = {};\r\n\r\n# new ArrayList()    没有指定长度时\r\nprivate static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};\r\n\r\n# 最大长度（比较重要）\r\nprivate static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\r\n```\r\n### 普通成员\r\n注意不是使用泛型数组  而是使用 Object[]数组\r\n```\r\n# 存储数据的数组\r\ntransient Object[] elementData; // non-private to simplify nested class access\r\n\r\n# 记录当前存储元素个数\r\nprivate int size;\r\n```\r\n\r\n### 构造函数\r\n**无参构造**\r\n	- 默认初始化DEFAULTCAPACITY_EMPTY_ELEMENTDATA={}\r\n```\r\n    public ArrayList() {\r\n        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;\r\n    }\r\n```\r\n\r\n**有参构造**\r\n	- 如果指定了长度  就立即初始化为当前长度（当要添加大数据量的时候，可以避免重复扩容）\r\n	- 如果指定长度为0  则为EMPTY_ELEMENTDATA={}\r\n```\r\n    public ArrayList(int initialCapacity) {\r\n        if (initialCapacity > 0) {\r\n            this.elementData = new Object[initialCapacity];\r\n        } else if (initialCapacity == 0) {\r\n            this.elementData = EMPTY_ELEMENTDATA;\r\n        } else {\r\n            throw new IllegalArgumentException(\"Illegal Capacity: \"+\r\n                                               initialCapacity);\r\n        }\r\n    }\r\n```\r\n\r\n### grow()扩容函数\r\n只得注意的是，位运算的优先级低于加减法，所以需要括号括起来，否则会变成（oldCapacity+oldCapacity）/2，失去了扩容的效果\r\n```java\r\n    private void grow(int minCapacity) {\r\n        // overflow-conscious code\r\n        int oldCapacity = elementData.length;\r\n        //扩容到1.5倍\r\n        int newCapacity = oldCapacity + (oldCapacity >> 1);\r\n        //如果不能扩容到1.5倍  则扩容到当前期望的minCapacity\r\n        if (newCapacity - minCapacity < 0)\r\n            newCapacity = minCapacity;\r\n\r\n        if (newCapacity - MAX_ARRAY_SIZE > 0)\r\n            newCapacity = hugeCapacity(minCapacity);\r\n        // minCapacity is usually close to size, so this is a win:\r\n        elementData = Arrays.copyOf(elementData, newCapacity);\r\n    }\r\n```\r\n\r\n## 如何保证增删改查\r\n#### 添加元素\r\n1. 末尾插入\r\n检查当前数组长度  确保内部容量   如果不足size+1 则grow扩容\r\n```java\r\n    public boolean add(E e) {\r\n    	// 1. 检查当前数组长度  确保内部容量   如果不足size+1 则grow扩容\r\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\r\n        elementData[size++] = e;\r\n        return true;\r\n    }\r\n	// 3. 懒加载的实现部分  如果new ArrayList()就会在这里实现懒加载\r\n	private static int calculateCapacity(Object[] elementData, int minCapacity) {\r\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\r\n            return Math.max(DEFAULT_CAPACITY, minCapacity);\r\n        }\r\n        return minCapacity;\r\n    }\r\n	// 2. calculateCapacity会返回能够承载当前元素的容量（默认值10 或者 minCapacity）\r\n    private void ensureCapacityInternal(int minCapacity) {\r\n        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));\r\n    }\r\n    // 4. 如果最小容量大于数组长度 则需要扩容\r\n    private void ensureExplicitCapacity(int minCapacity) {\r\n        modCount++;\r\n\r\n        // overflow-conscious code\r\n        if (minCapacity - elementData.length > 0)\r\n            grow(minCapacity);\r\n    }\r\n```\r\n2. 指定位置插入\r\n> System.arraycopy(原数组, 原数组开始索引, 目标数组,目标数组开始索引,复制长度);\r\n\r\n这里我们可以好好研究一下为什么System.arraycopy(elementData, index, elementData, index + 1,size - index);将从index开始的元素全部往后移了一位。\r\n例如：  arr= [4,8,7,4,5,3,null,null,...] ，此时size=6 \r\n如果 我们想在**index**为**2** ，即**8**的后面添加元素**1**   只需要将数组变成 arr2= [4,8,7,7,4,5,3,null,...] ，然后将**arr2[2]=2**即可\r\n==怎么实现呢？==\r\n我们需要  **[2,5]**   的元素向后移动**1**格   也就是从**2（index）**开始   拷贝到从 **index+1** 开始的空间中\r\n并且移动的元素有{7,4，5,3}共   **4（size-index）**  个元素\r\n\r\n```java\r\n   public void add(int index, E element) {\r\n      rangeCheckForAdd(index);\r\n      //检查当前数组长度  确保内部容量   如果不足size+1 则grow扩容\r\n      ensureCapacityInternal(size + 1);  // Increments modCount!!\r\n      //从index开始  所有元素后移一个\r\n      System.arraycopy(elementData, index, elementData, index + 1,\r\n                       size - index);\r\n      elementData[index] = element;\r\n      size++;\r\n  }\r\n   public void add(int index, E element) {\r\n        rangeCheckForAdd(index);\r\n\r\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\r\n        //从index开始  所有元素后移一个\r\n        System.arraycopy(elementData, index, elementData, index + 1,\r\n                         size - index);\r\n        elementData[index] = element;\r\n        size++;\r\n    }\r\n```\r\n\r\n#### 删除元素\r\n因为只用移动index后面的元素向前一格   故移动的数量是  int numMoved = size - index - 1;\r\n```java\r\n    public E remove(int index) {\r\n        rangeCheck(index);\r\n        modCount++;\r\n        E oldValue = elementData(index);\r\n        int numMoved = size - index - 1;\r\n        if (numMoved > 0)//将index后面的元素向前移动\r\n            System.arraycopy(elementData, index+1, elementData, index,\r\n                             numMoved);\r\n        elementData[--size] = null; // clear to let GC do its work\r\n        return oldValue;\r\n    }\r\n    \r\n    public boolean remove(Object o) {\r\n        if (o == null) {\r\n            for (int index = 0; index < size; index++)\r\n                if (elementData[index] == null) {\r\n                    fastRemove(index);\r\n                    return true;\r\n                }\r\n        } else {\r\n            for (int index = 0; index < size; index++)\r\n                if (o.equals(elementData[index])) {\r\n                    fastRemove(index);\r\n                    return true;\r\n                }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private void fastRemove(int index) {\r\n        modCount++;\r\n        int numMoved = size - index - 1;\r\n        if (numMoved > 0)\r\n            System.arraycopy(elementData, index+1, elementData, index,\r\n                             numMoved);\r\n        elementData[--size] = null; // clear to let GC do its work\r\n    }\r\n```\r\n\r\n\r\n# ArrayList简单实现\r\n\r\n```java\r\nimport java.util.Arrays;\r\n\r\n/**\r\n * @Author MinXu\r\n * @Date 2020/12/21 23:16\r\n * QQ   754647431\r\n */\r\npublic class MyList<E> {\r\n    //Default capacity\r\n    private static final int DEFAULT_CAPACITY = 10;\r\n\r\n    private static final Object[] EMPTY_ELEMENTDATA = {};\r\n\r\n    private Object[] elementData;\r\n\r\n    private int size;\r\n\r\n    public MyList() {\r\n        elementData = EMPTY_ELEMENTDATA;\r\n    }\r\n\r\n    public MyList(int capacity) {\r\n        if (capacity > 0)\r\n            elementData = new Object[capacity];\r\n        else\r\n            elementData = EMPTY_ELEMENTDATA;\r\n    }\r\n\r\n    void ensureCapacity(int minCapacity) {\r\n        if(elementData==EMPTY_ELEMENTDATA){\r\n            elementData=new Object[DEFAULT_CAPACITY];\r\n            return;\r\n        }\r\n        //需要扩容\r\n        if (minCapacity >= elementData.length) {\r\n            //加法优先级高于位运算\r\n            int oneAndHalf = elementData.length + (elementData.length >> 1);\r\n            Object[] a;\r\n            if (minCapacity > oneAndHalf) {\r\n                a = new Object[minCapacity];\r\n            } else {\r\n                a = new Object[oneAndHalf];\r\n            }\r\n            System.arraycopy(elementData,0,a,0,size);\r\n            elementData=a;\r\n        }\r\n    }\r\n    void checkIndex(int index){\r\n        if(index<0)\r\n            throw new IndexOutOfBoundsException(\"索引小于0\");\r\n        if(index>size-1)\r\n            throw new IndexOutOfBoundsException(\"索引越界\");\r\n    }\r\n    public void add(E e) {\r\n        ensureCapacity(size + 1);\r\n        elementData[size++]=e;\r\n    }\r\n    public E remove(int index) {\r\n        checkIndex(index);\r\n        E t=(E)elementData[index];\r\n        System.arraycopy(elementData,index+1,elementData,index,size-index-1);\r\n        //设置为空 以免产生内存溢出\r\n        elementData[size-1]=null;\r\n        size--;\r\n        return t;\r\n    }\r\n    public void add(int index,E e) {\r\n        checkIndex(index);\r\n        ensureCapacity(size + 1);\r\n\r\n        System.arraycopy(elementData,index,elementData,index+1,size-index);\r\n        elementData[index]=e;\r\n        size++;\r\n    }\r\n\r\n    public E get(int index) {\r\n        checkIndex(index);\r\n        return (E)elementData[index];\r\n    }\r\n    public int size() {\r\n        return this.size;\r\n    }\r\n    public void print() {\r\n        System.out.println(Arrays.toString(elementData));\r\n    }\r\n    @Override\r\n    public String toString() {\r\n        if(size==0)\r\n            return \"[]\";\r\n        StringBuilder res = new StringBuilder(\"[\");\r\n        for(int i=0;i<size-1;i++){\r\n            res.append(elementData[i].toString()).append(\", \");\r\n        }\r\n        res.append(elementData[size-1].toString());\r\n        res.append(\"]\");\r\n        return res.toString();\r\n    }\r\n}\r\n\r\n```\r\n\r\n\r\n', '2021-11-18 09:47:12', 'http://znbiscool.icu/859d25a7-2e0b-45b4-a0aa-a345ee8305a42541754405BFBA024904176594A5217E7558AE690F.jpg', 0);
INSERT INTO `article` VALUES (13, 2, 0, 'JDK源码学习03-ThreadLocal源码简析', '# JDK源码学习03-ThreadLocal源码简析\r\n> 在读完源码之后值得注意的细节：\r\n> 1. 在get方法中，先获取map，如果map不为空才就从map中取，如果map为空，就返回initialValue（）\r\n> 2. 每个线程使用完ThreadLocal之后，需要调用ThreadLocal的remove方法\r\n## ThreadLocal简介\r\n多个线程可能都需要某些相同的属性，我们可以在集成Thead类或者实现Runnable的时候，给类赋予私有属性，但是这样做比较呆板，于是ThreadLocal就出现了。比如在Java的数据库连接池中，多个线程会被分别分配一个connection对象，而一个线程必然从始至终都是只使用一个connection对象的（我们可以想想为什么？比如两个线程使用一个connection对象对数据库进行事务操作，就乱套了）。此时我们希望可以给每个线程设置自己的专属属性，就使用ThreadLocal来实现。\r\n\r\n**但需要注意的是，ThreadLocal会出现内存泄漏的情况，下面也会详细结合JVM的GC讲解。**\r\n\r\nThreadLocal其实原理很简单，主要就是在Thread类中本来就拥有一个属性,这个属性就专属于这个线程。学习Java时间久了一点，发现Java确实一切皆对象，Class是对象，Void是对象，线程Thread肯定也是对象。\r\n既然ThreadLocalMap是每个线程都有的私有对象，说明该对象不存在线程竞争问题，并且生命周期从创建（使用ThreadLocal的set或者withInitial会初始化这个map对象），如果不手动删除的话，会一直到线程结束才停止。\r\n**比如：**\r\n >  我们把每个线程想象成一个人，而\r\n\r\n**十个人竞争一个共享单车，这是不安全的（并且每个人没有自己的独特性）。但是线程的属性 ThreadLocal.ThreadLocalMap threadLocals = null;的出现相当于给每个人都配备了一个停车位，可以允许每个人拥有自行车。而ThreadLocal的静态方法withInitial则实现了给threadLocals赋值初始值，相当于给每个人的车位都配一辆基础版自行车，这样每个人用自己的自行车就可以了。并且每个人都可以对自己的自行车进行改装，并不会影响别人的自行车。就算没有使用静态方法withInitial，就算大家都没有被分配基础版自行车，但还是有停车位，可以通过ThreadLocal的set方法，给自己购买一辆自行车。**\r\n```\r\n ThreadLocal.ThreadLocalMap threadLocals = null;\r\n ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;\r\n```\r\n> 看下面的代码，我们创建了一个ThreadLocal<Student> threadLocal对象，并且被两个线程先后访问。第一个线程修改了threadLocal的值，第二个线程再次访问的时候，发现获得的还是初始值。这是因为ThreadLocal给每个线程都初始化了一个ThreadLocalMap，里面的key是ThreadLocal对象，而value是学生对象，所以尽管线程A先于线程B访问并修改这个对象，但其实修改的是线程ThreadA.ThreadLocalMap中的kv对threadLocal->student中的student对象的值。而线程二读取的是ThreadB.ThreadLocalMap中的kv对threadLocal->student中的student对象的值。\r\n```java\r\n	static ThreadLocal<Student> threadLocal = ThreadLocal.withInitial(()->new Student(\"minxu\"));\r\n    public static void main(String[] args) {\r\n        new Thread(()->{\r\n            System.out.println(Thread.currentThread()+\"开始干活。。。\");\r\n            Student student = threadLocal.get();\r\n            student.setName(\"heiheihei\");\r\n            System.out.println(student);\r\n        },\"A\").start();\r\n        new Thread(()->{\r\n            sleep(1000);\r\n            System.out.println(   Thread.currentThread()+\"开始干活。。。\");\r\n            Student student = threadLocal.get();\r\n            System.out.println(Thread.currentThread()+\" \"+student);\r\n        },\"B\").start();\r\n        sleep(1500);\r\n    }\r\n    public static void sleep(int time){\r\n        try {\r\n            Thread.sleep(time);\r\n        } catch (InterruptedException e) {\r\n            e.printStackTrace();\r\n        }\r\n    }\r\n```\r\n输出：\r\nThread[A,5,main]开始干活。。。\r\nStudent{name=\'heiheihei\'}\r\nThread[B,5,main]开始干活。。。\r\nThread[B,5,main] Student{name=\'minxu\'}\r\n\r\n<hr>\r\n<hr>\r\n<hr>\r\n\r\n\r\n## 下面我们来讲讲实现的过程和代码\r\n\r\n然后以ThreadLocal作为key，将数据存储进入每个线程中，这样就不存在进程关系。\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210313125737268.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n## 简单使用实例\r\n\r\n\r\n## 源码查看\r\n核心代码很简单，只有下面两三个函数\r\n有手就能看懂，，，\r\n```java\r\n	// 本函数保证了只初始化一次和懒加载\r\n	public T get() {\r\n        Thread t = Thread.currentThread();\r\n        //ThreadLocalMap getMap(Thread t) {return t.threadLocals;}\r\n        ThreadLocalMap map = getMap(t);\r\n        if (map != null) {\r\n            ThreadLocalMap.Entry e = map.getEntry(this);\r\n            if (e != null) {\r\n                @SuppressWarnings(\"unchecked\")\r\n                T result = (T)e.value;\r\n                return result;\r\n            }\r\n        }\r\n        return setInitialValue();\r\n    }\r\n	private T setInitialValue() {\r\n        T value = initialValue();\r\n        Thread t = Thread.currentThread();\r\n        ThreadLocalMap map = getMap(t);\r\n        if (map != null)\r\n            map.set(this, value);\r\n        else\r\n            createMap(t, value);\r\n        return value;\r\n    }\r\n```\r\n\r\n\r\n## ThreadLocal内存泄漏原理\r\n> 如果是强引用（实际是弱引用） --ThreadLocalMap中的key指向了ThreadLocal对象\r\n```\r\n我们知道JVM的gc只会对一个跟不可达的对象进行一次fanalize检查一次gc回收。\r\n\r\n   当线程Thread运行的时候，线程的属性ThreadLocal.ThreadLocalMap threadLocals是不会回收的，\r\n不仅ThreadLocal的引用指向了ThreadLocal对象，而ThreadLocalMap中的key指向了ThreadLocal对象，\r\n（我们可以先假设key-》ThreadLocal是强引用，为了改进此问题，实际使用的是弱引用）。所以即使ThreadLocal使用完毕\r\n即ThreadLocal的引用不再指向了ThreadLocal对象，，由于还有key指向ThreadLocal对象，也没有办法回收ThreadLocal，\r\n只有等Thread运行完才能回收。Thread的生命周期是很长的，所以这就叫做内存泄漏，无用对象无法回收。\r\n```\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210402124506710.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n\r\n> 实际是弱引用    --==还是存在问题==\r\n\r\n```\r\n    我们将ThreadLocalMap中的key指向了ThreadLocal对象的引用改成弱引用\r\n这样在栈中的强引用挂了之后，gc扫描到ThreadLocal对象就会把ThreadLocal对象回收了\r\n\r\n```\r\n==**这样看起来是不是没问题了？但是我们忽略了一件事，就是key虽然被回收了，但是value指向的对象没有被回收！！！！**==      也就是说ThreadLocalMap中的value如果不手动删除  如果线程生命很长，就会产生内存泄漏问题。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210402124928727.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n', '2021-11-18 09:48:09', 'http://znbiscool.icu/3d8ff33c-13af-4282-a286-d6d5c594062d1489037721108.jpg', 0);
INSERT INTO `article` VALUES (14, 2, 0, 'JDK源码学习04-寄生虫LinkedHashMap', '# 活久见的LinkedHashMap\r\n## 随便聊聊\r\n笔者大三，在2021.1.18入职字节实习生，干了四十天就休假跑路回去上课了。最近一大段时间过的非常松懈，现在报复性疯狂发布博客，哈哈。还是决定阅读JDK源码，从集合开始，希望能够读完核心的集合、AQS、线程池在等部分，最近对Java并发比较感兴趣也比较有体会，鬼知道能不能坚持到那个时候。笔者的文章可能不够详细，但会记录下来个人觉得比较核心的内容。\r\n联系方式：qq 754647431\r\n\r\n# LinkedHashMap简介\r\n==核心原理：==\r\n> **LinkedHashMap的底层结构是：由于继承而产生的HashMap父类｜LinkedHashMap自己的双向链表**\r\n> 所以LinkedHashMap只不过是在HashMap的基础上额外维护了一个双向链表罢了，仅此而已\r\n> 那么问题来了：LinkedHashMap是如何在自己实现一点点方法的基础上，利用到HashMap中呢？\r\n> 	-----实际上是LinkedHashMap重写了HashMap的newNode函数，额外添加了一句话linkNodeLast(p);这样就实现了在插入时维护双向链表，但是删除元素非常复杂，需要仔细看源码才行。这里先放下HashMap留给LinkedHashMap的后门方法。\r\n\r\n```\r\n    # 这是HashMap留给LinkedHashMap的后门，\r\n    // Callbacks to allow LinkedHashMap post-actions\r\n    // 如果hashmap的节点被更新  则调用该方法\r\n    void afterNodeAccess(Node<K,V> p) { }\r\n    // 如果hashmap插入节点   则调用该方法\r\n    void afterNodeInsertion(boolean evict) { }\r\n    // 如果hashmap删除节点  则调用该方法\r\n    void afterNodeRemoval(Node<K,V> p) { }\r\n```\r\n这是我调试的时候截的图，可以看出，LinkedHashMap按顺序存储了双向链表结构，而HashMap也按hash存储了数据，每个数据都被存储了两次，并且结构体也不同\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2021040216294681.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n\r\n\r\n1. 我们可以发现LinkedHashMap继承于HashMap，在HashMap已经实现Map接口的情况下，LinkedHashMap仍然实现了Map接口，实现Map接口会要求LinkedHashMap实现所有Map接口的方法。\r\n```\r\npublic class LinkedHashMap<K,V>\r\n    extends HashMap<K,V>\r\n    implements Map<K,V>\r\n```\r\n2. 我们可以发现，LinkedHashMap包含的函数非常少，甚至不包含put方法，这是因为LinkedHashMap继承了HashMap的put等方法。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210402151235410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n3. 我们来看看LinkedHashMap如何从HashMap获取数据，来维护自己的双向链表。\r\n- LinkedHashMap重写了HashMap的newNode方法，每次HashMap需要插入节点的时候都需要newNode函数，这样LinkedHashMap就能够通过linkNodeLast(p);来维护双向链表了。\r\n```java\r\n	Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {\r\n        LinkedHashMap.Entry<K,V> p =\r\n            new LinkedHashMap.Entry<K,V>(hash, key, value, e);\r\n        linkNodeLast(p);\r\n        return p;\r\n    }\r\n```\r\n==核心维护双向链表的函数==\r\n```java\r\n// link at the end of list\r\n    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {\r\n        // 获取双向链表尾节点  用来实现尾插\r\n        LinkedHashMap.Entry<K,V> last = tail;\r\n        // 尾节点指向新节点\r\n        tail = p;\r\n        // 如果双向链表尾空  设置头节点指向新节点  此时头尾都指向新节点\r\n        if (last == null)\r\n            head = p;\r\n        else {\r\n            // 原尾节点与新节点双向奔赴\r\n            p.before = last;\r\n            last.after = p;\r\n        }\r\n    }\r\n```\r\n\r\n', '2021-11-18 09:48:52', 'http://znbiscool.icu/798b10e6-2fe3-41e7-b71c-78d8cbed95d31557970701399.jpeg', 0);
INSERT INTO `article` VALUES (15, 2, 0, 'JDK源码学习05-简单理解AQS和FairSync源码', '# JDK源码学习05-简单理解AQS和FairSync源码\r\n## 1. 简介\r\n### 1.1 说明\r\nReentrantLock的实现依托于大名鼎鼎AQS实现，AQS的注释上说了，建议使用内部工具类的方式实现AQS抽象类，作为同步工具。所以ReentrantLock内部主要有NonfairSync（非公平锁）和FairSync（公平锁）两种实现方式。\r\nAQS就是AbstractQueuedSynchronizer即抽象队列同步器，使用模版方法设计模式，由AQS提供同步和核心算法，而把核心代码中调用的关键函数留给继承的子类来实现，这就是模版方法设计模式。DougLea建议使用实现AQS的内部类作为工具类来实现自定义的同步类。\r\n### 1.2 透过FairSync理解AQS\r\n我们可以看到ReentrantLock只不过是实现了Lock接口罢了，所以ReentrantLock的锁功能实现必然不因为Lock接口，那是怎么实现的呢？\r\n```\r\npublic class ReentrantLock implements Lock, java.io.Serializable \r\n```\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210410194600747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MTE3NjU1,size_16,color_FFFFFF,t_70)\r\n### 1.3 ReentrantLock的内部类\r\nReentrantLock内部主要有NonfairSync（非公平锁）和FairSync（公平锁）两种实现方式。\r\n我们可以看见ReentrantLock内部主要有三个内部类。\r\n```\r\n# 由于公平锁和非公平锁有一些相似的功能，可以抽象起来\r\nabstract static class Sync extends AbstractQueuedSynchronizer {\r\n# 非公平锁\r\nstatic final class NonfairSync extends Sync {\r\n# 公平锁\r\nstatic final class FairSync extends Sync {\r\n```\r\n\r\n## 2. 顺着逻辑理清FairSync脉络\r\n2.1 根据构造函数选择公平或是非公平锁\r\n```java\r\n    public ReentrantLock() {\r\n        sync = new NonfairSync();\r\n    }\r\n    public ReentrantLock(boolean fair) {\r\n        sync = fair ? new FairSync() : new NonfairSync();\r\n    }\r\n```\r\n2.2 上锁\r\n接下来的流程非常重要，建议仔细阅读每一步的注释\r\n\r\n1. 首先我们知道lock，初始时如果获取到锁，就不会阻塞；如果初始时没有获取到锁，就会阻塞\r\n2. 表面上，上面一句话是废话，但是在追读源码的时候很容易忽视\r\n3. 清楚上面之后，下面的acquire函数的功能就非常容易解释了，获取到锁就放行，把它当作一个空函数；没获取到锁就阻塞，一直阻塞到获取到锁，然后放行。（这就是锁的究极含义）\r\n```java\r\n    public void lock() {\r\n        sync.lock();\r\n    }\r\n    // FairSync中的函数\r\n    final void lock() {\r\n        acquire(1);// AQS函数\r\n    }\r\n    // AQS中的函数   接下来就是重点了  我们仔细追踪每一步流程\r\n    // - \r\n    public final void acquire(int arg) {\r\n    	// 4. 先使用tryAcquire尝试获取锁，如果获取到，if条件整个为false，就可以直接放行了，就很棒。\r\n    	// 5. 如果tryAcquire没有获取到锁，就会执行后面的条件了，首先执行的是addWaiter函数，addWaiter函数\r\n    	//    的参数只是用来标记状态的，不需要关心。现在先执行addWaiter函数。\r\n        if (!tryAcquire(arg) &&\r\n            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\r\n            selfInterrupt();\r\n    }\r\n```\r\n4. 现在我们细看上面的步骤，先使用tryAcquire尝试获取锁，如果获取到，if条件整个为false，就可以直接放行了，就很棒。tryAcquire是AQS提供给我们实现的方法，用来实现自己的锁逻辑，我们先简略看看即可，不必深究。下面代码的逻辑非常复杂，特别是hasQueuedPredecessors中有很多种情况，我们留着后面再解析（这不过是实现公平锁特有的代码段，而我们更希望研究调用这个tryAcquire函数的框架AQS----模版方法设计模式）\r\n```java\r\n    protected final boolean tryAcquire(int acquires) {\r\n        final Thread current = Thread.currentThread();\r\n        int c = getState();\r\n        if (c == 0) {\r\n        	// hasQueuedPredecessors的英文含义是已经给队列排队前置节点\r\n        	// 在队列不是空的情况下 如果当前线程是第一个排队的节点（即链表第二个节点） 函数返回true否则false\r\n            if (!hasQueuedPredecessors() &&\r\n                compareAndSetState(0, acquires)) {\r\n                setExclusiveOwnerThread(current);\r\n                return true;\r\n            }\r\n        }\r\n        else if (current == getExclusiveOwnerThread()) {\r\n            int nextc = c + acquires;\r\n            if (nextc < 0)\r\n                throw new Error(\"Maximum lock count exceeded\");\r\n            setState(nextc);\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n    public final boolean hasQueuedPredecessors() {\r\n        // The correctness of this depends on head being initialized\r\n        // before tail and on head.next being accurate if the current\r\n        // thread is first in queue.\r\n        Node t = tail; // Read fields in reverse initialization order\r\n        Node h = head;\r\n        Node s;\r\n        return h != t &&\r\n            ((s = h.next) == null || s.thread != Thread.currentThread());\r\n    }\r\n```\r\n\r\n5. 如果tryAcquire没有获取到锁，就会执行后面的条件了，首先执行的是addWaiter函数，addWaiter函数的参数只是用来标记状态的，不需要关心。现在先执行addWaiter函数，addWaiter的意思就是把当前线程添加到同步队列的尾部。\r\n```java\r\n	private Node addWaiter(Node mode) {\r\n        // 获取锁失败的线程会执行到这里 并且自己把自己入队\r\n        Node node = new Node(Thread.currentThread(), mode);\r\n        // Try the fast path of enq; backup to full enq on failure\r\n        Node pred = tail;\r\n        // 如果tail不为null  先尝试在本函数入队  失败就使用enq循环入队 鬼知道为什么要这多余的一步\r\n        if (pred != null) {\r\n            // 进队操作  参考enq函数的else\r\n            node.prev = pred;\r\n            if (compareAndSetTail(pred, node)) {\r\n                pred.next = node;\r\n                return node;\r\n            }\r\n        }\r\n        // 函数就在下方\r\n        enq(node);\r\n        return node;\r\n    }\r\n    private Node enq(final Node node) {\r\n        for (;;) {\r\n            Node t = tail;\r\n            // 只有在第一次进队才出现t为null的情况  \r\n            // 初始队头--》象征获取锁的线程 但不是  队头node中thread永远为null\r\n            // 初始化第一次的头节点之后  就可以真正进队当前节点了（第二次for循环）\r\n            if (t == null) { // Must initialize\r\n                if (compareAndSetHead(new Node()))\r\n                    tail = head;\r\n            } else {\r\n                // 一直到进队成功  才结束函数\r\n                node.prev = t;\r\n                if (compareAndSetTail(t, node)) {\r\n                    t.next = node;\r\n                    return t;\r\n                }\r\n            }\r\n        }\r\n    }\r\n```\r\n6. 上面的代码看起来很长，其实也就是 死循环+CAS 将当前线程加入队尾罢了。\r\n\r\n```\r\n# 唯一需要注意的是，AQS中的头节点是懒加载的形式，阻塞的节点只能从第二个节点开始排队。\r\n```\r\n7. 此时我们再次回到第三步，及下述代码，此时我们可以将条件简化了。我们假设有人先获取了锁，然后我才来，此时tryAcquire返回false，所以现在我们来研究以下acquireQueued函数\r\n```java\r\nif (!tryAcquire(arg) &&\r\n            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\r\n            selfInterrupt();\r\nif (acquireQueued(new Node(“我是进来排队的线程”), arg))\r\n            selfInterrupt();\r\n```\r\n    // 死循环！！  此处的node是上一部中被添加到AQS尾部的节点\r\n    // 此时是tryAcquire失败后 第一次尝试再次尝试获取锁（由于是公平锁 则必须是第一个排队的人）\r\n    // 如果获取失败，就会进入下一个判断\r\n    // shouldParkAfterFailedAcquire我们简要说明一下 \r\n    //虽然还包含了取消节点的功能  但这里不扩展了 只是为了方便理解的说明\r\n    // 只有在前一个节点状态是-1的时候 才会返回true   如果前一个节点状态不为-1  则设置为-1\r\n    //       第一次节点状态默认是0  shouldParkAfterFailedAcquire检查之后设为-1\r\n    // 故循环一般会被循环一次  两次  在第两次的时候  才会调用parkAndCheckInterrupt方法  进入阻塞\r\n    // 如果我这个线程被唤醒  很明显 通过下面逻辑  本函数返回的一定是false\r\n```java\r\n    final boolean acquireQueued(final Node node, int arg) {\r\n        boolean failed = true;\r\n        try {\r\n            boolean interrupted = false;\r\n            // 死循环！！  此处的node是上一部中被添加到AQS尾部的节点\r\n            // 此时是tryAcquire失败后 第一次尝试再次尝试获取锁（由于是公平锁 则必须是第一个排队的人）\r\n            // 如果获取失败，就会进入下一个判断\r\n            // shouldParkAfterFailedAcquire我们简要说明一下 \r\n            //虽然还包含了取消节点的功能  但这里不扩展了 只是为了方便理解的说明\r\n            // 只有在前一个节点状态是-1的时候 才会返回true   如果前一个节点状态不为-1  则设置为-1\r\n            //       第一次节点状态默认是0  shouldParkAfterFailedAcquire检查之后设为-1\r\n            // 故循环一般会被循环一次  两次  在第两次的时候  才会调用parkAndCheckInterrupt方法  进入阻塞\r\n            // 如果我这个线程被唤醒  很明显 通过下面逻辑  本函数返回的一定是false\r\n            for (;;) {\r\n                final Node p = node.predecessor();\r\n                if (p == head && tryAcquire(arg)) {\r\n                    setHead(node);\r\n                    p.next = null; // help GC\r\n                    failed = false;\r\n                    return interrupted;\r\n                }\r\n                if (shouldParkAfterFailedAcquire(p, node) &&\r\n                    parkAndCheckInterrupt())\r\n                    interrupted = true;\r\n            }\r\n        } finally {\r\n            if (failed)\r\n                cancelAcquire(node);\r\n        }\r\n    }\r\n	// 这个函数不是实际源码  而是我删减后的\r\n    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {\r\n        int ws = pred.waitStatus;\r\n        if (ws == Node.SIGNAL)\r\n            return true;\r\n        else {\r\n            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);\r\n        }\r\n        return false;\r\n    }\r\n    private final boolean parkAndCheckInterrupt() {\r\n        LockSupport.park(this);\r\n        return Thread.interrupted();\r\n    }\r\n```\r\n8. 现在我们基本分析完成了整个AQS的流程\r\n\r\n# 3. 暂未写完，留待更新', '2021-11-18 09:49:25', NULL, 0);
INSERT INTO `article` VALUES (16, 2, 0, 'MySQL复习之读书笔记', '# 深入了解InnoDB-技术内幕InnoDB\r\nmysql复习两本书：技术内幕innodb，mysql王者晋级之路，逐步更新\r\n复习顺序：\r\n- 一直复习算法\r\n- 复习mysql、redis、计网、JVM、多线程、Nginx、Spring、设计模式、操作系统\r\n- 学习MQ\r\n\r\n# MySQL介绍\r\nMySQL的体系结构可以分为两层，MySQL Server层和存储引擎层。在MySQL Server层中又包括连接层和SQL层。SQL层包含权限判断、查询缓存、解析器、预处理、查询优化器、缓存和执行计划。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/ecacec07ecee4a269be718f82dd79768.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n\r\n\r\n# 1. 核心重点列举\r\n1. mysql通过mvcc多版本并发控制提升并发性，默认隔离级别**可重复读**，通过next-key解决幻读现象，InnoDB引擎提供了插入缓冲（change buffer直接在缓存里修改最后落盘，合并写操作），二次写double write（内存2M硬盘2M，落盘的时候先将脏页全部顺序写入硬盘为顺序io，然后将脏页随机io刷盘，这样刷盘时挂机还可以从第一次写的数据重新刷盘，预防脏页数据写一半挂机的情况 ps：操作系统页4k mysql页16k），自适应哈希索引，预读等高性能和高可用的功能。\r\n2. MyISAM引擎，不支持事务，表锁，只缓冲索引文件，索引文件末端直接指向数据地址。表由MYD和MYI组成D是data，I是Index。\r\n3. InnoDB引擎，其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读，同时被设计用来最有效地利用以及使用内存和CPU。\r\n4. truncate不能回滚和delete\r\n5. 具体来看，缓冲池中缓存的数据页类型有:索引页、数据页、undo 页、插入缓冲\r\n(insert buffer  【增删改的change buffer】只能非聚簇非唯一索引使用  结构为b+树）、自适应哈希索引(adaptive hash index)、InnoDB 存储的锁信息(lock info)、数据字典信息(data dictionary)等。不能简单地认为，缓冲池只是缓存索引页和数据页，它们只是占缓冲池很大的一部分而已。图2-2很好地显示了InnoDB存储引擎中内存的结构情况。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/91a6d83a534a44f5bf456880873c3275.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n6. 缓冲池5/8 3/8的LRU算法\r\n7. write ahead log。事务提交时，先写重做日志redolog再修改页。\r\n8. check point   阶段性的刷新脏页到内存\r\n\r\n```\r\n# 因此Checkpoint (检查点)技术的目的是解决以下几个问题:\r\n	- 缩短数据库的恢复时间;\r\n	- 缓冲池不够用时，将脏页刷新到磁盘;\r\n	- 重做日志redolog不可用时，刷新脏页。\r\n# 当数据库发生宕机时，数据库不需要重做redo所有的日志，因为Checkpoint之前的页都\r\n已经刷新回磁盘。故数据库只需对Checkpoint后的重做日志进行恢复。这样就大大缩短\r\n了恢复的时间。\r\n# 此外，当缓冲池不够用时，根据LRU算法会溢出最近最少使用的页，若此页为脏\r\n页，那么需要强制执行Checkpoint，将脏页也就是页的新版本刷回磁盘。\r\n```\r\n8. LSN日志序列号\r\n9. 日志\r\n- 错误日志(error log)\r\n- 二进制日志(binlog) 逻辑日志 主从同步 crash恢复 事务\r\n事务开启会有一个binlog cache size默认32k，事务内容全写入，该cache是基于会话session的，提交后持久化道磁盘（缓冲写，攒多少事务，即使有事务还没commit也会刷盘（需要另一个参数innodb support xa解决），提交之后不一定立刻刷盘）\r\n- 慢查询日志(slow query log)\r\n- 查询日志(log) \r\n\r\n10. 文件，表结构文件frm，视图结构文件frm，MyISAM的文件为MYD、MYI，InnoDB文件为IBD，默认有ibdata1为表空间，也可是多文件组成表空间\r\n# 两次写\r\n脏页刷盘时先写到double wrtite buffer，然后先顺序刷盘（备份），再离散写入脏页。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f6f2dc5315fd4ffcb49bd52351c371ad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n\r\n# 2. Redo Log重做日志\r\n> 循环写入 物理日志  是脏页数据修改在磁盘的备份\r\n> redo log和对bufferpool中数据修改息息相关\r\n\r\n当实例或介质失败( media failure) 时，重做日志文件就能派上用场。例如，数据库由于所在主机掉电导致实例失败，InnoDB存储引擎会使用重做日志恢复到掉电前的时刻，以此来保证数据的完整性。每个InnoDB存储引擎至少有1个重做日志文件组(group)， 每个文件组下至少有2个重做日志文件，如默认的ib_ logfile0和ib_ logfle1。**循环写入**。\r\n如果重做日志写满了，有capacity变量可知晓，需要将脏页刷新到磁盘。**redo log存在的意义就是记录磁盘中的数据页和脏页的物理区别（数据页->脏页（最新数据）需要什么操作），优点就是脏页缓存了真实页数据的修改但是在内存中容易丢失，redo log可以与脏页一一对应再次备份一下脏页的修改，这样系统更健壮不会丢数据。所以脏页刷盘之后，那一页对应的redo log就无用了。配合两次写，两次写保证修改后的最新数据页不丢失，redo log保证修改的操作不丢失**\r\n==每秒redo log buffer刷盘一次==\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/4f55e255fb07400eb124470873a82e75.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n\r\n# 索引\r\nB+树索引、全文索引、哈希索引。  联合索引/多列索引、覆盖索引、倒排索引、前缀索引\r\n\r\n- 聚簇索引即是主键索引是b+树结构叶子结点为数据页\r\n- 辅助索引是b+数结构叶子结点为主键，所以存在**回表**，select的值是该{辅助索引的键值(可1-n个)，表的主键值(可1-n个)}这个集合的子集就是**覆盖索引**\r\n- 倒排索引：如果想查询包含什么单词的文章，不是扫描所有文章，而是提前建好倒排索引，倒排索引的key是单词，value是文章ID\r\n\r\n- mysql为什么使用b+树？可以减少io次数（3-4层即可实现千万行数据级别），页之间有双向指针可以范围查询。顺序插入时复杂度低\r\n- 执行计划：可能用到的索引、实际用到的索引、印象行数、表名、extra、type\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/97324d3972a3404fb47ea675c0b6f549.png)\r\n\r\n\r\n**需要牢记的是，B+ 树索引本身并不能找到具体的一条记录，能找到只是该记录所在的页**。数据库把页载人到内存，然后通过Page Directory再进行二叉查找。只不过二叉查找的时间复杂度很低，同时在内存中的查找很快，因此通常忽略这部分查找所用的时间。\r\n\r\n# MySQL两阶段提交过程:\r\n两阶段提交分别为prepare和commit阶段。\r\n- 准备阶段(transaction prepare):\r\n事务SQL语句先写入redo log buffer，然后做一个事务准备标记，再将logbuffer中的数据刷新到redolog。\r\n- 提交阶段(commit): 将事务产生的binlog写入文件，刷入磁盘。\r\n- 再在redo log中做一个事务提交的标记，并把binlog写成功的标记也一并写入 redo log文件。\r\n我们结合以下场景分析两阶段提交如何保证数据库的一致性。\r\n\r\n# 锁\r\n## 关键点\r\n- 一致性锁定读 和 非一致性锁定度，前者是加锁读，后者是快照读。加锁读的含义是加锁（select 。 for update｜select 。lock in share mode）然后读，快照读的含义就是多版本，使用roll pointer和trx id标记的行数据的多版本，read commit读已提交读取当前能读到的已提交的最新版本，repeatable read 总是读取事务开始时的版本行\r\n\r\nInnoDB存储弓I擎支持意向锁设计比较简练，其意向锁即为表级别的锁。设计目的主\r\n要是为了在-一个事务中揭示下一-行将被请求的锁类型。其支持两种意向锁:\r\n- 意向共享锁(IS Lock)， 事务想要获得- -张表中某几行的共享锁\r\n- 意向排他锁(IX Lock),事务想要获得- -张表中某几行的排他锁\r\n\r\nInnoDB存储弓|擎实现了如下两种标准的行级锁:\r\n- 共享锁(S Lock)， 允许事务读- -行数据。\r\n- 排他锁(X Lock)，允许事务删除或更新- -行数据。\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f54deff36dc546358dc4e6344ef000e2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n## 锁的算法\r\n### 行锁的算法\r\nInnoDB存储引擎有3种行锁的算法，其分别是:\r\n- Record Lock:单个行记录上的锁\r\n- Gap Lock:间隙锁，锁定一个范围，但不包含记录本身\r\n- Next-Key Lock : Gap Lock+Record Lock，锁定一个范围，并且锁定记录本身\r\n\r\n## 锁的问题\r\n脏读\r\n不可重复读\r\n丢失更新\r\n阻塞\r\n死锁：死锁是指两个或两个以.上的事务在执行过程中，因争夺锁资源而造成的一种互相等待的现象。\r\n\r\n# 事务\r\n# 事务的特性ACID\r\n - 原子： 全成功全失败\r\n - 一致：一种状态到另一种状态，约束不被破坏\r\n - 隔离：并发控制、串行化、锁\r\n - 持久：事务一旦提交永久改变\r\n\r\n## 事务的隔离级别\r\n> 实际上mysql在可重复读隔离级别就解决了幻读问题，通过next-key\r\n\r\n| 隔离级别 | 问题 |\r\n|--|--|\r\n| 读未提交 | 脏读 不可重复度 幻读 |\r\n| 读已提交 | 不可重复度 幻读 |\r\n| 可重复读 | 幻读 |\r\n| 可串行化 | 无问题 |\r\n\r\n## 事务的实现\r\n事务隔离性由第6章讲述的锁来实现。原子性、一致性、持久性通过数据库的redo log和undo log来完成。redo log称为重做日志，用来保证事务的原子性和持久性。undolog用来保证事务的一致性。有的DBA或许会认为undo是redo的逆过程，其实不然。redo和undo的作用都可以视为是一种恢复操作，redo恢复提交事务修改的页操作，而undo回滚行记录到某个特定版本。因此两者记录的内容不同，redo通常是物理日志，记录的是页的物理修改操\r\n作。undo 是逻辑日志，根据每行记录进行记录。\r\n# MVCC\r\nread view。   trx id。roll pointer\r\n\r\n', '2021-11-18 09:50:29', 'http://znbiscool.icu/83983bc3-fc02-4e92-9506-19a803522dd21580891399699.jpg', 0);
INSERT INTO `article` VALUES (17, 2, 0, '阿里云CentOS部署Java项目全流程', '# 阿里云CentOS部署Java项目全流程\r\n\r\n服务器需要具备的东西：\r\n安全组开放相应的端口\r\n关闭防火墙\r\nMysql数据库\r\njdk1.8\r\nspringboot项目的jar包\r\n\r\n# 1. 服务器安装jdk1.8\r\n```\r\nyum list java*\r\nyum install java-1.8.0-openjdk.x86_64\r\njava -version\r\n```\r\n# 2. 安装Mysql\r\n## 2.1 下载Mysql\r\n\r\n进入mysql官网，由于是centos8，所以选择第一个，点击Download跳转之后，直接点击【No thanks, just start my download.】即可不登录就下载。主要下载这四个lib、client、common 、server安装包并依次安装，互相之间有依赖所以有安装顺序，全部安装成功即可。\r\nhttps://dev.mysql.com/downloads/mysql/\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/4205f28c285e44f4b1812dc5fff44d4d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n- 可以wget下载\r\n```\r\nwget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-libs-8.0.27-1.el8.x86_64.rpm\r\nwget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-client-8.0.27-1.el8.x86_64.rpm\r\nwget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-common-8.0.27-1.el8.x86_64.rpm\r\nwget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-server-8.0.27-1.el8.x86_64.rpm\r\n```\r\n- 也可以本地下载，然后使用scp上传到服务器上即可\r\n```\r\n前面填写本地路径 后面填写服务器路径即可 xftp上传也行\r\nscp ~/Downloads/mysql.xxxxxx root@39.xxx.187.218:/root/ \r\n输入密码\r\n```\r\n## 2.2 安装mysql\r\n- 安装刚刚上传的的mysql，需要注意顺序安装，下面的顺序可能不够正确，自行调整即可\r\n```\r\n yum localinstall mysql-community-libs-8.0.27-1.el8.x86_64.rpm\r\n yum localinstall mysql-community-client-8.0.27-1.el8.x86_64.rpm\r\n yum localinstall mysql-community-common-8.0.27-1.el8.x86_64.rpm\r\n yum localinstall mysql-community-server-8.0.27-1.el8.x86_64.rpm\r\n```\r\n## 2.3 启动Mysql\r\n- 启动mysql，获取mysql的密码\r\n```\r\nservice mysqld start\r\nless /var/log/mysqld.log|grep root@localhost\r\nmysql -uroot -p【图片中的密码】\r\n```\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/7f55701bac0b4854ae07b1c6e3aa00df.png)\r\n然后修改密码即可，建议百度，以下仅供参考\r\n```\r\nshow variables like \'validate_password%\';\r\nset global validate_password.policy=0;\r\nset global validate_password.length=1;\r\nalter user \'root\'@\'localhost\' identified by \'1234\';\r\n```\r\n- 设置允许远程连接\r\n```\r\nuse mysql;\r\nupdate user set host = ’%’ where user = ’root’;\r\nflush privileges;\r\n```\r\n## 2.4 导入数据\r\nnavicat很容易操作导出数据\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/83266acfff70401ba79000facf56ef89.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n反键新数据库即可插入\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/39422603e16041ff9277c5eb4c5205a5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n# 3. 部署项目\r\n## 3.1 导出jar包\r\n点击package即可，然后从target目录下拷贝出jar包，并上传到服务器\r\nscp即可完成上传\r\n```\r\nscp /Users/bytedance/IdeaProjects/jvav/target/jvav.jar root@39.xxx.187.218:/root/\r\n```\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/59ebfccae04d4a7d80a681505c599d28.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n## 3.2 执行项目\r\n以下命令中，nohup可以让项目一直后台运行，--server.port=8888可以修改项目的端口， -Dspring.config.location能够修改配置文件的路径。提前在本地准备好再编译部署非常麻烦，所以指定配置文件路径就很棒，能够任意修改配置文件而不用重新编译上传。\r\n注意：上一步编译的时候没有指定链接的数据库更新为服务器上的数据库，所以这里再次上传application.yml文件，vim更新\r\n```\r\n# 直接运行\r\nnohup java -jar XXX.jar > Log.log 2>&1 & \r\n# 指定端口运行\r\nnohup java -jar XXX.jar --server.port=8888 > Log.log 2>&1 & \r\n#指定配置文件运行\r\nnohup  java -jar -Dspring.config.location=路径  项目名.jar  --server.port=8888  > Log.log 2>&1 & \r\n```\r\n- 此时我们访问ip：port发现并不能访问到，因为服务器默认打开了防火墙\r\n```\r\n# 查看状态\r\nsystemctl status firewalld.service \r\n# 临时关闭\r\nsystemctl stop firewalld.service \r\n# 永久关闭\r\nsystemctl disable firewalld.service\r\n```\r\n# 4. 成功访问\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/8b592f356b4241a09efeced106bbfd99.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m57yW56iL55qETVg=,size_20,color_FFFFFF,t_70,g_se,x_16)\r\n\r\nmaven镜像的pom版本\r\n```\r\n    <repositories>\r\n        <repository>\r\n            <id>central</id>\r\n            <name>aliyun maven</name>\r\n            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>\r\n            <layout>default</layout>\r\n            <!-- 是否开启发布版构件下载 -->\r\n            <releases>\r\n                <enabled>true</enabled>\r\n            </releases>\r\n            <!-- 是否开启快照版构件下载 -->\r\n            <snapshots>\r\n                <enabled>false</enabled>\r\n            </snapshots>\r\n        </repository>\r\n    </repositories>\r\n```\r\n', '2021-11-18 09:51:02', 'http://znbiscool.icu/c658eaa5-4ad8-4c0b-b4b6-570eeb80be971605231307715.jpeg', 0);
COMMIT;

-- ----------------------------
-- Table structure for article_tag
-- ----------------------------
DROP TABLE IF EXISTS `article_tag`;
CREATE TABLE `article_tag` (
  `id` int NOT NULL AUTO_INCREMENT,
  `aid` int DEFAULT NULL,
  `tid` int DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `aid` (`aid`) USING BTREE,
  KEY `tid` (`tid`) USING BTREE,
  CONSTRAINT `article_tag_ibfk_1` FOREIGN KEY (`aid`) REFERENCES `article` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `article_tag_ibfk_2` FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of article_tag
-- ----------------------------
BEGIN;
INSERT INTO `article_tag` VALUES (1, 1, 1);
INSERT INTO `article_tag` VALUES (2, 1, 2);
INSERT INTO `article_tag` VALUES (3, 2, 1);
INSERT INTO `article_tag` VALUES (4, 2, 2);
INSERT INTO `article_tag` VALUES (5, 3, 3);
INSERT INTO `article_tag` VALUES (6, 3, 4);
INSERT INTO `article_tag` VALUES (7, 4, 3);
INSERT INTO `article_tag` VALUES (8, 4, 4);
INSERT INTO `article_tag` VALUES (9, 5, 3);
INSERT INTO `article_tag` VALUES (10, 6, 3);
INSERT INTO `article_tag` VALUES (11, 6, 6);
INSERT INTO `article_tag` VALUES (12, 7, 3);
INSERT INTO `article_tag` VALUES (16, 9, 3);
INSERT INTO `article_tag` VALUES (17, 8, 3);
INSERT INTO `article_tag` VALUES (18, 8, 7);
INSERT INTO `article_tag` VALUES (19, 8, 8);
INSERT INTO `article_tag` VALUES (22, 11, 1);
INSERT INTO `article_tag` VALUES (23, 11, 8);
INSERT INTO `article_tag` VALUES (29, 12, 1);
INSERT INTO `article_tag` VALUES (30, 12, 2);
INSERT INTO `article_tag` VALUES (31, 12, 4);
INSERT INTO `article_tag` VALUES (32, 12, 5);
INSERT INTO `article_tag` VALUES (33, 12, 8);
INSERT INTO `article_tag` VALUES (34, 13, 1);
INSERT INTO `article_tag` VALUES (35, 13, 2);
INSERT INTO `article_tag` VALUES (36, 13, 7);
INSERT INTO `article_tag` VALUES (37, 13, 8);
INSERT INTO `article_tag` VALUES (38, 14, 1);
INSERT INTO `article_tag` VALUES (39, 14, 2);
INSERT INTO `article_tag` VALUES (40, 14, 7);
INSERT INTO `article_tag` VALUES (41, 14, 8);
INSERT INTO `article_tag` VALUES (42, 15, 1);
INSERT INTO `article_tag` VALUES (43, 15, 2);
INSERT INTO `article_tag` VALUES (44, 15, 5);
INSERT INTO `article_tag` VALUES (45, 15, 7);
INSERT INTO `article_tag` VALUES (46, 15, 8);
INSERT INTO `article_tag` VALUES (47, 16, 1);
INSERT INTO `article_tag` VALUES (48, 16, 5);
INSERT INTO `article_tag` VALUES (49, 16, 6);
INSERT INTO `article_tag` VALUES (50, 16, 7);
INSERT INTO `article_tag` VALUES (51, 16, 8);
COMMIT;

-- ----------------------------
-- Table structure for attention
-- ----------------------------
DROP TABLE IF EXISTS `attention`;
CREATE TABLE `attention` (
  `id` int NOT NULL AUTO_INCREMENT,
  `masteruid` int DEFAULT NULL,
  `doneuid` int DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of attention
-- ----------------------------
BEGIN;
INSERT INTO `attention` VALUES (1, 2, 1);
COMMIT;

-- ----------------------------
-- Table structure for comment
-- ----------------------------
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
  `id` int NOT NULL AUTO_INCREMENT,
  `aid` int DEFAULT NULL COMMENT '评论所在文章',
  `name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评论者名字',
  `date` timestamp NULL DEFAULT NULL,
  `toname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '被评论的名字',
  `content` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评论内容',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `aid` (`aid`) USING BTREE,
  CONSTRAINT `comment_ibfk_1` FOREIGN KEY (`aid`) REFERENCES `article` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of comment
-- ----------------------------
BEGIN;
INSERT INTO `comment` VALUES (1, 9, '我会jvav啊', '2021-06-16 08:04:22', NULL, 'aaaa');
INSERT INTO `comment` VALUES (2, 9, '我会jvav啊', '2021-06-16 08:04:26', '我会jvav啊', 'aaaa');
INSERT INTO `comment` VALUES (3, 9, '我会jvav啊', '2021-06-16 08:04:31', '我会jvav啊', 'aaaa');
INSERT INTO `comment` VALUES (4, 9, '我会jvav啊', '2021-06-16 08:04:34', NULL, 'xxxx');
COMMIT;

-- ----------------------------
-- Table structure for star
-- ----------------------------
DROP TABLE IF EXISTS `star`;
CREATE TABLE `star` (
  `id` int NOT NULL AUTO_INCREMENT,
  `aid` int DEFAULT NULL,
  `uid` int DEFAULT NULL,
  `star` int DEFAULT '0',
  `agree` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `aid` (`aid`) USING BTREE,
  KEY `uid` (`uid`) USING BTREE,
  CONSTRAINT `star_ibfk_1` FOREIGN KEY (`aid`) REFERENCES `article` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `star_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of star
-- ----------------------------
BEGIN;
INSERT INTO `star` VALUES (1, 9, 1, 0, 1);
INSERT INTO `star` VALUES (2, 8, 1, 1, 0);
INSERT INTO `star` VALUES (3, 7, 1, 0, 1);
INSERT INTO `star` VALUES (4, 6, 1, 1, 0);
INSERT INTO `star` VALUES (5, 5, 1, 0, 1);
COMMIT;

-- ----------------------------
-- Table structure for tag
-- ----------------------------
DROP TABLE IF EXISTS `tag`;
CREATE TABLE `tag` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of tag
-- ----------------------------
BEGIN;
INSERT INTO `tag` VALUES (1, '数据结构');
INSERT INTO `tag` VALUES (2, '算法');
INSERT INTO `tag` VALUES (3, '微服务');
INSERT INTO `tag` VALUES (4, 'Spring');
INSERT INTO `tag` VALUES (5, '多线程');
INSERT INTO `tag` VALUES (6, '数据库');
INSERT INTO `tag` VALUES (7, '计算机基础');
INSERT INTO `tag` VALUES (8, '学习面试心得');
COMMIT;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名称',
  `avatar` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户头像',
  `username` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `password` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `introduction` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '暂无个人资料',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, '我会jvav啊', 'https://pic1.zhimg.com/da8e974dc_im.jpg', 'admin', 'mx', '中国地质大学（武汉） 软件工程');
INSERT INTO `user` VALUES (2, 'mx', 'https://pic1.zhimg.com/da8e974dc_im.jpg', 'mx', 'mx', '中国地质大学（未来城校区）');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
